diff options
author | Zhidao HONG <z.hong@f5.com> | 2021-04-29 22:04:34 +0800 |
---|---|---|
committer | Zhidao HONG <z.hong@f5.com> | 2021-04-29 22:04:34 +0800 |
commit | 53279af5d44dce2b679399d6a36eb46292928175 (patch) | |
tree | 973ba2979096f6969d11a8646151034e8a4372fd /src | |
parent | 113afb09ea7ddeebf2376cf6df3af212705e6128 (diff) | |
download | unit-53279af5d44dce2b679399d6a36eb46292928175.tar.gz unit-53279af5d44dce2b679399d6a36eb46292928175.tar.bz2 |
Static: support for openat2() features.
Support for chrooting, rejecting symlinks, and rejecting crossing mounting
points on a per-request basis during static file serving.
Diffstat (limited to '')
-rw-r--r-- | src/nxt_conf_validation.c | 32 | ||||
-rw-r--r-- | src/nxt_errno.h | 1 | ||||
-rw-r--r-- | src/nxt_file.c | 44 | ||||
-rw-r--r-- | src/nxt_file.h | 32 | ||||
-rw-r--r-- | src/nxt_http.h | 2 | ||||
-rw-r--r-- | src/nxt_http_route.c | 63 | ||||
-rw-r--r-- | src/nxt_http_static.c | 123 | ||||
-rw-r--r-- | src/nxt_unix.h | 4 |
8 files changed, 274 insertions, 27 deletions
diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index 8c5d1ec7..ac1a81d8 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -75,6 +75,8 @@ static nxt_int_t nxt_conf_vldt_error(nxt_conf_validation_t *vldt, const char *fmt, ...); static nxt_int_t nxt_conf_vldt_var(nxt_conf_validation_t *vldt, 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, 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"), .type = NXT_CONF_VLDT_OBJECT, .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 @@ -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 nxt_conf_vldt_var(nxt_conf_validation_t *vldt, const char *option, nxt_str_t *value) diff --git a/src/nxt_errno.h b/src/nxt_errno.h index 40bcfa3f..ec700537 100644 --- a/src/nxt_errno.h +++ b/src/nxt_errno.h @@ -22,6 +22,7 @@ typedef int nxt_err_t; #define NXT_EACCES EACCES #define NXT_EBUSY EBUSY #define NXT_EEXIST EEXIST +#define NXT_ELOOP ELOOP #define NXT_EXDEV EXDEV #define NXT_ENOTDIR ENOTDIR #define NXT_EISDIR EISDIR diff --git a/src/nxt_file.c b/src/nxt_file.c index a9595dd9..5d38d57e 100644 --- a/src/nxt_file.c +++ b/src/nxt_file.c @@ -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 nxt_file_close(nxt_task_t *task, nxt_file_t *file) { diff --git a/src/nxt_file.h b/src/nxt_file.h index 4f56e746..4846305b 100644 --- a/src/nxt_file.h +++ b/src/nxt_file.h @@ -109,6 +109,12 @@ typedef struct { 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); +#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. */ #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_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. */ #define NXT_FILE_CREATE_OR_OPEN O_CREAT #define NXT_FILE_OPEN 0 diff --git a/src/nxt_http.h b/src/nxt_http.h index 2aa108ec..da1124a8 100644 --- a/src/nxt_http.h +++ b/src/nxt_http.h @@ -217,6 +217,8 @@ struct nxt_http_action_s { } app; struct { + nxt_str_t chroot; + nxt_uint_t resolve; nxt_http_action_t *fallback; } share; } u; diff --git a/src/nxt_http_route.c b/src/nxt_http_route.c index dfdb07df..bfe5ce5f 100644 --- a/src/nxt_http_route.c +++ b/src/nxt_http_route.c @@ -50,8 +50,11 @@ typedef struct { nxt_conf_value_t *pass; nxt_conf_value_t *ret; nxt_str_t location; - nxt_conf_value_t *share; 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_http_route_action_conf_t; @@ -637,6 +640,21 @@ static nxt_conf_map_t nxt_http_route_action_conf[] = { 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_CONF_MAP_PTR, offsetof(nxt_http_route_action_conf_t, fallback) @@ -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_action_t *action) { +#if (NXT_HAVE_OPENAT2) + u_char *p; + uint8_t slash; + nxt_str_t *chroot; +#endif nxt_mp_t *mp; nxt_int_t ret; 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) { 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) { action->u.share.fallback = nxt_mp_alloc(mp, sizeof(nxt_http_action_t)); diff --git a/src/nxt_http_static.c b/src/nxt_http_static.c index c0b48586..98d70739 100644 --- a/src/nxt_http_static.c +++ b/src/nxt_http_static.c @@ -31,15 +31,15 @@ nxt_http_action_t * nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, nxt_http_action_t *action) { - size_t alloc, encode; - u_char *p; + size_t length, encode; + u_char *p, *fname; struct tm tm; nxt_buf_t *fb; nxt_int_t ret; - nxt_str_t index, extension, *mtype; + nxt_str_t index, extension, *mtype, *chroot; nxt_uint_t level; nxt_bool_t need_body; - nxt_file_t *f; + nxt_file_t *f, af, file; nxt_file_info_t fi; nxt_http_field_t *field; 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; } - 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] == '/') { /* TODO: dynamic index setting. */ 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); } - alloc = action->name.length + r->path->length + index.length + 1; + f = NULL; - f->name = nxt_mp_nget(r->mem_pool, alloc); - if (nxt_slow_path(f->name == NULL)) { + length = action->name.length + r->path->length + index.length; + + fname = nxt_mp_nget(r->mem_pool, length + 1); + if (nxt_slow_path(fname == NULL)) { goto fail; } - p = f->name; + p = fname; p = nxt_cpymem(p, action->name.start, action->name.length); p = nxt_cpymem(p, r->path->start, r->path->length); p = nxt_cpymem(p, index.start, index.length); *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)) { - switch (f->error) { + + switch (file.error) { /* * 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; case NXT_EACCES: +#if (NXT_HAVE_OPENAT2) + case NXT_ELOOP: + case NXT_EXDEV: +#endif level = NXT_LOG_ERR; status = NXT_HTTP_FORBIDDEN; break; @@ -132,13 +189,27 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, } 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); 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); if (nxt_slow_path(ret != NXT_OK)) { 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"); - 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)) { goto fail; } 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_size(&fi)) - 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"); 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) { - 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)) { goto fail; } field->value = p; - field->value_length = alloc; + field->value_length = length; if (encode > 0) { 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); - if (f != NULL && f->fd != NXT_FILE_INVALID) { + if (f != NULL) { nxt_file_close(task, f); } diff --git a/src/nxt_unix.h b/src/nxt_unix.h index 609f7e95..393f61d9 100644 --- a/src/nxt_unix.h +++ b/src/nxt_unix.h @@ -242,6 +242,10 @@ #include <sys/mount.h> #endif +#if (NXT_HAVE_OPENAT2) +#include <linux/openat2.h> +#endif + #if (NXT_TEST_BUILD) #include <nxt_test_build.h> #endif |