Static: support for openat2() features.

Support for chrooting, rejecting symlinks, and rejecting crossing mounting
points on a per-request basis during static file serving.
This commit is contained in:
Zhidao HONG
2021-04-29 22:04:34 +08:00
parent 113afb09ea
commit 53279af5d4
10 changed files with 313 additions and 27 deletions

View File

@@ -49,3 +49,35 @@ nxt_feature_test="#include <fcntl.h>
return 0; return 0;
}" }"
. auto/feature . auto/feature
nxt_feature="openat2()"
nxt_feature_name=NXT_HAVE_OPENAT2
nxt_feature_run=
nxt_feature_incs=
nxt_feature_libs=
nxt_feature_test="#include <fcntl.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/openat2.h>
#include <string.h>
int main() {
struct open_how how;
memset(&how, 0, sizeof(how));
how.flags = O_RDONLY;
how.mode = O_NONBLOCK;
how.resolve = RESOLVE_IN_ROOT
| RESOLVE_NO_SYMLINKS
| RESOLVE_NO_XDEV;
int fd = syscall(SYS_openat2, AT_FDCWD, \".\",
&how, sizeof(how));
if (fd == -1)
return 1;
return 0;
}"
. auto/feature

View File

@@ -31,6 +31,13 @@ NGINX Unit updated to 1.24.0.
date="" time="" date="" time=""
packager="Andrei Belov &lt;defan@nginx.com&gt;"> packager="Andrei Belov &lt;defan@nginx.com&gt;">
<change type="feature">
<para>
support for chrooting, rejecting symlinks, and rejecting crossing mounting
points on a per-request basis during static file serving.
</para>
</change>
</changes> </changes>

View File

@@ -75,6 +75,8 @@ static nxt_int_t nxt_conf_vldt_error(nxt_conf_validation_t *vldt,
const char *fmt, ...); const char *fmt, ...);
static nxt_int_t nxt_conf_vldt_var(nxt_conf_validation_t *vldt, static nxt_int_t nxt_conf_vldt_var(nxt_conf_validation_t *vldt,
const char *option, nxt_str_t *value); const char *option, nxt_str_t *value);
nxt_inline nxt_int_t nxt_conf_vldt_unsupported(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data);
static nxt_int_t nxt_conf_vldt_mtypes(nxt_conf_validation_t *vldt, static nxt_int_t nxt_conf_vldt_mtypes(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data); nxt_conf_value_t *value, void *data);
@@ -458,6 +460,27 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_share_action_members[] = {
.name = nxt_string("fallback"), .name = nxt_string("fallback"),
.type = NXT_CONF_VLDT_OBJECT, .type = NXT_CONF_VLDT_OBJECT,
.validator = nxt_conf_vldt_action, .validator = nxt_conf_vldt_action,
}, {
.name = nxt_string("chroot"),
.type = NXT_CONF_VLDT_STRING,
#if !(NXT_HAVE_OPENAT2)
.validator = nxt_conf_vldt_unsupported,
.u.string = "chroot",
#endif
}, {
.name = nxt_string("follow_symlinks"),
.type = NXT_CONF_VLDT_BOOLEAN,
#if !(NXT_HAVE_OPENAT2)
.validator = nxt_conf_vldt_unsupported,
.u.string = "follow_symlinks",
#endif
}, {
.name = nxt_string("traverse_mounts"),
.type = NXT_CONF_VLDT_BOOLEAN,
#if !(NXT_HAVE_OPENAT2)
.validator = nxt_conf_vldt_unsupported,
.u.string = "traverse_mounts",
#endif
}, },
NXT_CONF_VLDT_END NXT_CONF_VLDT_END
@@ -1032,6 +1055,15 @@ nxt_conf_vldt_error(nxt_conf_validation_t *vldt, const char *fmt, ...)
} }
nxt_inline nxt_int_t
nxt_conf_vldt_unsupported(nxt_conf_validation_t *vldt, nxt_conf_value_t *value,
void *data)
{
return nxt_conf_vldt_error(vldt, "Unit is built without the \"%s\" "
"option support.", data);
}
static nxt_int_t static nxt_int_t
nxt_conf_vldt_var(nxt_conf_validation_t *vldt, const char *option, nxt_conf_vldt_var(nxt_conf_validation_t *vldt, const char *option,
nxt_str_t *value) nxt_str_t *value)

View File

@@ -22,6 +22,7 @@ typedef int nxt_err_t;
#define NXT_EACCES EACCES #define NXT_EACCES EACCES
#define NXT_EBUSY EBUSY #define NXT_EBUSY EBUSY
#define NXT_EEXIST EEXIST #define NXT_EEXIST EEXIST
#define NXT_ELOOP ELOOP
#define NXT_EXDEV EXDEV #define NXT_EXDEV EXDEV
#define NXT_ENOTDIR ENOTDIR #define NXT_ENOTDIR ENOTDIR
#define NXT_EISDIR EISDIR #define NXT_EISDIR EISDIR

View File

@@ -42,6 +42,50 @@ nxt_file_open(nxt_task_t *task, nxt_file_t *file, nxt_uint_t mode,
} }
#if (NXT_HAVE_OPENAT2)
nxt_int_t
nxt_file_openat2(nxt_task_t *task, nxt_file_t *file, nxt_uint_t mode,
nxt_uint_t create, nxt_file_access_t access, nxt_fd_t dfd,
nxt_uint_t resolve)
{
struct open_how how;
nxt_memzero(&how, sizeof(how));
/* O_NONBLOCK is to prevent blocking on FIFOs, special devices, etc. */
mode |= (O_NONBLOCK | create);
how.flags = mode;
how.mode = access;
how.resolve = resolve;
file->fd = syscall(SYS_openat2, dfd, file->name, &how, sizeof(how));
file->error = (file->fd == -1) ? nxt_errno : 0;
#if (NXT_DEBUG)
nxt_thread_time_update(task->thread);
#endif
nxt_debug(task, "openat2(%FD, \"%FN\"): %FD err:%d", dfd, file->name,
file->fd, file->error);
if (file->fd != -1) {
return NXT_OK;
}
if (file->log_level != 0) {
nxt_log(task, file->log_level, "openat2(%FD, \"%FN\") failed %E", dfd,
file->name, file->error);
}
return NXT_ERROR;
}
#endif
void void
nxt_file_close(nxt_task_t *task, nxt_file_t *file) nxt_file_close(nxt_task_t *task, nxt_file_t *file)
{ {

View File

@@ -109,6 +109,12 @@ typedef struct {
NXT_EXPORT nxt_int_t nxt_file_open(nxt_task_t *task, nxt_file_t *file, NXT_EXPORT nxt_int_t nxt_file_open(nxt_task_t *task, nxt_file_t *file,
nxt_uint_t mode, nxt_uint_t create, nxt_file_access_t access); nxt_uint_t mode, nxt_uint_t create, nxt_file_access_t access);
#if (NXT_HAVE_OPENAT2)
NXT_EXPORT nxt_int_t nxt_file_openat2(nxt_task_t *task, nxt_file_t *file,
nxt_uint_t mode, nxt_uint_t create, nxt_file_access_t access, nxt_fd_t dfd,
nxt_uint_t resolve);
#endif
/* The file open access modes. */ /* The file open access modes. */
#define NXT_FILE_RDONLY O_RDONLY #define NXT_FILE_RDONLY O_RDONLY
@@ -116,6 +122,32 @@ NXT_EXPORT nxt_int_t nxt_file_open(nxt_task_t *task, nxt_file_t *file,
#define NXT_FILE_RDWR O_RDWR #define NXT_FILE_RDWR O_RDWR
#define NXT_FILE_APPEND (O_WRONLY | O_APPEND) #define NXT_FILE_APPEND (O_WRONLY | O_APPEND)
#if (NXT_HAVE_OPENAT2)
#if defined(O_DIRECTORY)
#define NXT_FILE_DIRECTORY O_DIRECTORY
#else
#define NXT_FILE_DIRECTORY 0
#endif
#if defined(O_SEARCH)
#define NXT_FILE_SEARCH (O_SEARCH|NXT_FILE_DIRECTORY)
#elif defined(O_EXEC)
#define NXT_FILE_SEARCH (O_EXEC|NXT_FILE_DIRECTORY)
#else
/*
* O_PATH is used in combination with O_RDONLY. The last one is ignored
* if O_PATH is used, but it allows Unit to not fail when it was built on
* modern system (i.e. glibc 2.14+) and run with a kernel older than 2.6.39.
* Then O_PATH is unknown to the kernel and ignored, while O_RDONLY is used.
*/
#define NXT_FILE_SEARCH (O_PATH|O_RDONLY|NXT_FILE_DIRECTORY)
#endif
#endif /* NXT_HAVE_OPENAT2 */
/* The file creation modes. */ /* The file creation modes. */
#define NXT_FILE_CREATE_OR_OPEN O_CREAT #define NXT_FILE_CREATE_OR_OPEN O_CREAT
#define NXT_FILE_OPEN 0 #define NXT_FILE_OPEN 0

View File

@@ -217,6 +217,8 @@ struct nxt_http_action_s {
} app; } app;
struct { struct {
nxt_str_t chroot;
nxt_uint_t resolve;
nxt_http_action_t *fallback; nxt_http_action_t *fallback;
} share; } share;
} u; } u;

View File

@@ -50,8 +50,11 @@ typedef struct {
nxt_conf_value_t *pass; nxt_conf_value_t *pass;
nxt_conf_value_t *ret; nxt_conf_value_t *ret;
nxt_str_t location; nxt_str_t location;
nxt_conf_value_t *share;
nxt_conf_value_t *proxy; nxt_conf_value_t *proxy;
nxt_conf_value_t *share;
nxt_str_t chroot;
nxt_conf_value_t *follow_symlinks;
nxt_conf_value_t *traverse_mounts;
nxt_conf_value_t *fallback; nxt_conf_value_t *fallback;
} nxt_http_route_action_conf_t; } nxt_http_route_action_conf_t;
@@ -636,6 +639,21 @@ static nxt_conf_map_t nxt_http_route_action_conf[] = {
NXT_CONF_MAP_PTR, NXT_CONF_MAP_PTR,
offsetof(nxt_http_route_action_conf_t, share) offsetof(nxt_http_route_action_conf_t, share)
}, },
{
nxt_string("chroot"),
NXT_CONF_MAP_STR,
offsetof(nxt_http_route_action_conf_t, chroot)
},
{
nxt_string("follow_symlinks"),
NXT_CONF_MAP_PTR,
offsetof(nxt_http_route_action_conf_t, follow_symlinks)
},
{
nxt_string("traverse_mounts"),
NXT_CONF_MAP_PTR,
offsetof(nxt_http_route_action_conf_t, traverse_mounts)
},
{ {
nxt_string("fallback"), nxt_string("fallback"),
NXT_CONF_MAP_PTR, NXT_CONF_MAP_PTR,
@@ -648,6 +666,11 @@ static nxt_int_t
nxt_http_route_action_create(nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv, nxt_http_route_action_create(nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv,
nxt_http_action_t *action) nxt_http_action_t *action)
{ {
#if (NXT_HAVE_OPENAT2)
u_char *p;
uint8_t slash;
nxt_str_t *chroot;
#endif
nxt_mp_t *mp; nxt_mp_t *mp;
nxt_int_t ret; nxt_int_t ret;
nxt_str_t name, *string; nxt_str_t name, *string;
@@ -720,6 +743,44 @@ nxt_http_route_action_create(nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv,
if (accf.share != NULL) { if (accf.share != NULL) {
action->handler = nxt_http_static_handler; action->handler = nxt_http_static_handler;
#if (NXT_HAVE_OPENAT2)
string = &accf.chroot;
chroot = &action->u.share.chroot;
if (string->length > 0) {
action->u.share.resolve |= RESOLVE_IN_ROOT;
slash = (string->start[string->length - 1] != '/');
chroot->length = string->length + (slash ? 1 : 0);
chroot->start = nxt_mp_alloc(mp, chroot->length + 1);
if (nxt_slow_path(chroot->start == NULL)) {
return NXT_ERROR;
}
p = nxt_cpymem(chroot->start, string->start, string->length);
if (slash) {
*p++ = '/';
}
*p = '\0';
}
if (accf.follow_symlinks != NULL
&& !nxt_conf_get_boolean(accf.follow_symlinks))
{
action->u.share.resolve |= RESOLVE_NO_SYMLINKS;
}
if (accf.traverse_mounts != NULL
&& !nxt_conf_get_boolean(accf.traverse_mounts))
{
action->u.share.resolve |= RESOLVE_NO_XDEV;
}
#endif
if (accf.fallback != NULL) { if (accf.fallback != NULL) {
action->u.share.fallback = nxt_mp_alloc(mp, action->u.share.fallback = nxt_mp_alloc(mp,
sizeof(nxt_http_action_t)); sizeof(nxt_http_action_t));

View File

@@ -31,15 +31,15 @@ nxt_http_action_t *
nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r,
nxt_http_action_t *action) nxt_http_action_t *action)
{ {
size_t alloc, encode; size_t length, encode;
u_char *p; u_char *p, *fname;
struct tm tm; struct tm tm;
nxt_buf_t *fb; nxt_buf_t *fb;
nxt_int_t ret; nxt_int_t ret;
nxt_str_t index, extension, *mtype; nxt_str_t index, extension, *mtype, *chroot;
nxt_uint_t level; nxt_uint_t level;
nxt_bool_t need_body; nxt_bool_t need_body;
nxt_file_t *f; nxt_file_t *f, af, file;
nxt_file_info_t fi; nxt_file_info_t fi;
nxt_http_field_t *field; nxt_http_field_t *field;
nxt_http_status_t status; nxt_http_status_t status;
@@ -63,13 +63,6 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r,
need_body = 1; need_body = 1;
} }
f = nxt_mp_zget(r->mem_pool, sizeof(nxt_file_t));
if (nxt_slow_path(f == NULL)) {
goto fail;
}
f->fd = NXT_FILE_INVALID;
if (r->path->start[r->path->length - 1] == '/') { if (r->path->start[r->path->length - 1] == '/') {
/* TODO: dynamic index setting. */ /* TODO: dynamic index setting. */
nxt_str_set(&index, "index.html"); nxt_str_set(&index, "index.html");
@@ -80,23 +73,83 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r,
nxt_str_null(&extension); nxt_str_null(&extension);
} }
alloc = action->name.length + r->path->length + index.length + 1; f = NULL;
f->name = nxt_mp_nget(r->mem_pool, alloc); length = action->name.length + r->path->length + index.length;
if (nxt_slow_path(f->name == NULL)) {
fname = nxt_mp_nget(r->mem_pool, length + 1);
if (nxt_slow_path(fname == NULL)) {
goto fail; goto fail;
} }
p = f->name; p = fname;
p = nxt_cpymem(p, action->name.start, action->name.length); p = nxt_cpymem(p, action->name.start, action->name.length);
p = nxt_cpymem(p, r->path->start, r->path->length); p = nxt_cpymem(p, r->path->start, r->path->length);
p = nxt_cpymem(p, index.start, index.length); p = nxt_cpymem(p, index.start, index.length);
*p = '\0'; *p = '\0';
ret = nxt_file_open(task, f, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0); nxt_memzero(&file, sizeof(nxt_file_t));
file.name = fname;
chroot = &action->u.share.chroot;
#if (NXT_HAVE_OPENAT2)
if (action->u.share.resolve != 0) {
if (chroot->length > 0) {
file.name = chroot->start;
if (length > chroot->length
&& nxt_memcmp(fname, chroot->start, chroot->length) == 0)
{
fname += chroot->length;
ret = nxt_file_open(task, &file, NXT_FILE_SEARCH, NXT_FILE_OPEN,
0);
} else {
file.error = NXT_EACCES;
ret = NXT_ERROR;
}
} else if (fname[0] == '/') {
file.name = (u_char *) "/";
ret = nxt_file_open(task, &file, NXT_FILE_SEARCH, NXT_FILE_OPEN, 0);
} else {
file.name = (u_char *) ".";
file.fd = AT_FDCWD;
ret = NXT_OK;
}
if (nxt_fast_path(ret == NXT_OK)) {
af = file;
nxt_memzero(&file, sizeof(nxt_file_t));
file.name = fname;
ret = nxt_file_openat2(task, &file, NXT_FILE_RDONLY,
NXT_FILE_OPEN, 0, af.fd,
action->u.share.resolve);
if (af.fd != AT_FDCWD) {
nxt_file_close(task, &af);
}
}
} else {
ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0);
}
#else
ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0);
#endif
if (nxt_slow_path(ret != NXT_OK)) { if (nxt_slow_path(ret != NXT_OK)) {
switch (f->error) {
switch (file.error) {
/* /*
* For Unix domain sockets "errno" is set to: * For Unix domain sockets "errno" is set to:
@@ -117,6 +170,10 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r,
break; break;
case NXT_EACCES: case NXT_EACCES:
#if (NXT_HAVE_OPENAT2)
case NXT_ELOOP:
case NXT_EXDEV:
#endif
level = NXT_LOG_ERR; level = NXT_LOG_ERR;
status = NXT_HTTP_FORBIDDEN; status = NXT_HTTP_FORBIDDEN;
break; break;
@@ -132,13 +189,27 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r,
} }
if (status != NXT_HTTP_NOT_FOUND) { if (status != NXT_HTTP_NOT_FOUND) {
nxt_log(task, level, "open(\"%FN\") failed %E", f->name, f->error); if (chroot->length > 0) {
nxt_log(task, level, "opening \"%FN\" at \"%FN\" failed %E",
fname, chroot, file.error);
} else {
nxt_log(task, level, "opening \"%FN\" failed %E",
fname, file.error);
}
} }
nxt_http_request_error(task, r, status); nxt_http_request_error(task, r, status);
return NULL; return NULL;
} }
f = nxt_mp_get(r->mem_pool, sizeof(nxt_file_t));
if (nxt_slow_path(f == NULL)) {
goto fail;
}
*f = file;
ret = nxt_file_info(f, &fi); ret = nxt_file_info(f, &fi);
if (nxt_slow_path(ret != NXT_OK)) { if (nxt_slow_path(ret != NXT_OK)) {
goto fail; goto fail;
@@ -172,15 +243,15 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r,
nxt_http_field_name_set(field, "ETag"); nxt_http_field_name_set(field, "ETag");
alloc = NXT_TIME_T_HEXLEN + NXT_OFF_T_HEXLEN + 3; length = NXT_TIME_T_HEXLEN + NXT_OFF_T_HEXLEN + 3;
p = nxt_mp_nget(r->mem_pool, alloc); p = nxt_mp_nget(r->mem_pool, length);
if (nxt_slow_path(p == NULL)) { if (nxt_slow_path(p == NULL)) {
goto fail; goto fail;
} }
field->value = p; field->value = p;
field->value_length = nxt_sprintf(p, p + alloc, "\"%xT-%xO\"", field->value_length = nxt_sprintf(p, p + length, "\"%xT-%xO\"",
nxt_file_mtime(&fi), nxt_file_mtime(&fi),
nxt_file_size(&fi)) nxt_file_size(&fi))
- p; - p;
@@ -254,19 +325,19 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r,
nxt_http_field_name_set(field, "Location"); nxt_http_field_name_set(field, "Location");
encode = nxt_encode_uri(NULL, r->path->start, r->path->length); encode = nxt_encode_uri(NULL, r->path->start, r->path->length);
alloc = r->path->length + encode * 2 + 1; length = r->path->length + encode * 2 + 1;
if (r->args->length > 0) { if (r->args->length > 0) {
alloc += 1 + r->args->length; length += 1 + r->args->length;
} }
p = nxt_mp_nget(r->mem_pool, alloc); p = nxt_mp_nget(r->mem_pool, length);
if (nxt_slow_path(p == NULL)) { if (nxt_slow_path(p == NULL)) {
goto fail; goto fail;
} }
field->value = p; field->value = p;
field->value_length = alloc; field->value_length = length;
if (encode > 0) { if (encode > 0) {
p = (u_char *) nxt_encode_uri(p, r->path->start, r->path->length); p = (u_char *) nxt_encode_uri(p, r->path->start, r->path->length);
@@ -294,7 +365,7 @@ fail:
nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR); nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
if (f != NULL && f->fd != NXT_FILE_INVALID) { if (f != NULL) {
nxt_file_close(task, f); nxt_file_close(task, f);
} }

View File

@@ -242,6 +242,10 @@
#include <sys/mount.h> #include <sys/mount.h>
#endif #endif
#if (NXT_HAVE_OPENAT2)
#include <linux/openat2.h>
#endif
#if (NXT_TEST_BUILD) #if (NXT_TEST_BUILD)
#include <nxt_test_build.h> #include <nxt_test_build.h>
#endif #endif