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:
32
auto/files
32
auto/files
@@ -49,3 +49,35 @@ nxt_feature_test="#include <fcntl.h>
|
||||
return 0;
|
||||
}"
|
||||
. 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
|
||||
|
||||
@@ -31,6 +31,13 @@ NGINX Unit updated to 1.24.0.
|
||||
date="" time=""
|
||||
packager="Andrei Belov <defan@nginx.com>">
|
||||
|
||||
<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>
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -636,6 +639,21 @@ static nxt_conf_map_t nxt_http_route_action_conf[] = {
|
||||
NXT_CONF_MAP_PTR,
|
||||
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,
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user