summaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorZhidao HONG <z.hong@f5.com>2021-04-29 22:04:34 +0800
committerZhidao HONG <z.hong@f5.com>2021-04-29 22:04:34 +0800
commit53279af5d44dce2b679399d6a36eb46292928175 (patch)
tree973ba2979096f6969d11a8646151034e8a4372fd /src
parent113afb09ea7ddeebf2376cf6df3af212705e6128 (diff)
downloadunit-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 'src')
-rw-r--r--src/nxt_conf_validation.c32
-rw-r--r--src/nxt_errno.h1
-rw-r--r--src/nxt_file.c44
-rw-r--r--src/nxt_file.h32
-rw-r--r--src/nxt_http.h2
-rw-r--r--src/nxt_http_route.c63
-rw-r--r--src/nxt_http_static.c123
-rw-r--r--src/nxt_unix.h4
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