Browse Source

[stat_cache] separate func for symlink policy chk

Note: historical ToC-ToU race condition still exists in implementation
server.follow-symlink = "disable" is not recommended (default: "enable")
personal/stbuehler/ci-build
Glenn Strauss 3 years ago
parent
commit
73bfee6308
  1. 8
      src/configfile.c
  2. 8
      src/http-header-glue.c
  3. 7
      src/http_chunk.c
  4. 10
      src/mod_compress.c
  5. 6
      src/mod_expire.c
  6. 12
      src/response.c
  7. 108
      src/stat_cache.c
  8. 5
      src/stat_cache.h

8
src/configfile.c

@ -366,9 +366,7 @@ static int config_insert(server *srv) {
s->use_ipv6 = (i == 0) ? 0 : srv->config_storage[0]->use_ipv6;
s->set_v6only = (i == 0) ? 1 : srv->config_storage[0]->set_v6only;
s->defer_accept = (i == 0) ? 0 : srv->config_storage[0]->defer_accept;
#ifdef HAVE_LSTAT
s->follow_symlink = 1;
#endif
s->kbytes_per_second = 0;
s->allow_http11 = 1;
s->etag_use_inode = 1;
@ -400,9 +398,7 @@ static int config_insert(server *srv) {
cv[20].destination = &(s->max_read_idle);
cv[21].destination = &(s->max_write_idle);
cv[22].destination = s->error_handler;
#ifdef HAVE_LSTAT
cv[24].destination = &(s->follow_symlink);
#endif
cv[25].destination = &(s->global_kbytes_per_second);
cv[26].destination = &(s->kbytes_per_second);
cv[27].destination = &(s->use_xattr);
@ -692,9 +688,7 @@ int config_setup_connection(server *srv, connection *con) {
PATCH(error_handler_404);
PATCH(error_intercept);
PATCH(errorfile_prefix);
#ifdef HAVE_LSTAT
PATCH(follow_symlink);
#endif
PATCH(server_tag);
PATCH(kbytes_per_second);
PATCH(global_kbytes_per_second);
@ -771,10 +765,8 @@ int config_patch_connection(server *srv, connection *con) {
PATCH(etag_use_mtime);
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("etag.use-size"))) {
PATCH(etag_use_size);
#ifdef HAVE_LSTAT
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.follow-symlink"))) {
PATCH(follow_symlink);
#endif
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.name"))) {
buffer_copy_buffer(con->server_name, s->server_name);
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.tag"))) {

8
src/http-header-glue.c

@ -443,9 +443,8 @@ void http_response_send_file (server *srv, connection *con, buffer *path) {
return;
}
/* we only handline regular files */
#ifdef HAVE_LSTAT
if ((sce->is_symlink == 1) && !con->conf.follow_symlink) {
if (!con->conf.follow_symlink
&& 0 != stat_cache_path_contains_symlink(srv, path)) {
con->http_status = 403;
if (con->conf.log_request_handling) {
@ -455,7 +454,8 @@ void http_response_send_file (server *srv, connection *con, buffer *path) {
return;
}
#endif
/* we only handle regular files */
if (!S_ISREG(sce->st.st_mode)) {
con->http_status = 403;

7
src/http_chunk.c

@ -35,10 +35,9 @@ static void http_chunk_append_len(server *srv, connection *con, uintmax_t len) {
}
static int http_chunk_append_file_open_fstat(server *srv, connection *con, buffer *fn, struct stat *st) {
if (!con->conf.follow_symlink) {
/*(preserve existing stat_cache symlink checks)*/
stat_cache_entry *sce;
if (HANDLER_ERROR == stat_cache_get_entry(srv, con, fn, &sce)) return -1;
if (!con->conf.follow_symlink
&& 0 != stat_cache_path_contains_symlink(srv, fn)) {
return -1;
}
return stat_cache_open_rdonly_fstat(fn, st, con->conf.follow_symlink);

10
src/mod_compress.c

@ -863,11 +863,6 @@ PHYSICALPATH_FUNC(mod_compress_physical) {
}
/* we only handle regular files */
#ifdef HAVE_LSTAT
if ((sce->is_symlink == 1) && !con->conf.follow_symlink) {
return HANDLER_GO_ON;
}
#endif
if (!S_ISREG(sce->st.st_mode)) {
return HANDLER_GO_ON;
}
@ -942,6 +937,11 @@ PHYSICALPATH_FUNC(mod_compress_physical) {
const char *compression_name = NULL;
int compression_type = 0;
if (!con->conf.follow_symlink
&& 0 != stat_cache_path_contains_symlink(srv, con->physical.path)) {
return HANDLER_GO_ON;
}
mtime = strftime_cache_get(srv, sce->st.st_mtime);
/* try matching original etag of uncompressed version */

6
src/mod_expire.c

@ -361,9 +361,6 @@ CONNECTION_FUNC(mod_expire_handler) {
time_t ts, expires;
stat_cache_entry *sce = NULL;
/* if stat fails => sce == NULL, ignore return value */
(void) stat_cache_get_entry(srv, con, con->physical.path, &sce);
switch(mod_expire_get_offset(srv, p, vb, &ts)) {
case 0:
/* access */
@ -372,6 +369,9 @@ CONNECTION_FUNC(mod_expire_handler) {
case 1:
/* modification */
/* if stat fails => sce == NULL, ignore return value */
(void) stat_cache_get_entry(srv, con, con->physical.path, &sce);
/* can't set modification based expire header if
* mtime is not available
*/

12
src/response.c

@ -234,8 +234,8 @@ static handler_t http_response_physical_path_check(server *srv, connection *con)
}
}
#ifdef HAVE_LSTAT
if ((sce->is_symlink != 0) && !con->conf.follow_symlink) {
if (!con->conf.follow_symlink
&& 0 != stat_cache_path_contains_symlink(srv, con->physical.path)) {
con->http_status = 403;
if (con->conf.log_request_handling) {
@ -245,8 +245,8 @@ static handler_t http_response_physical_path_check(server *srv, connection *con)
buffer_reset(con->physical.path);
return HANDLER_FINISHED;
};
#endif
}
if (S_ISDIR(sce->st.st_mode)) {
if (con->uri.path->ptr[buffer_string_length(con->uri.path) - 1] != '/') {
/* redirect to .../ */
@ -255,11 +255,7 @@ static handler_t http_response_physical_path_check(server *srv, connection *con)
return HANDLER_FINISHED;
}
#ifdef HAVE_LSTAT
} else if (!S_ISREG(sce->st.st_mode) && !sce->is_symlink) {
#else
} else if (!S_ISREG(sce->st.st_mode)) {
#endif
/* any special handling of non-reg files ?*/
}

108
src/stat_cache.c

@ -575,19 +575,6 @@ const buffer * stat_cache_etag_get(stat_cache_entry *sce, etag_flags_t flags) {
return NULL;
}
#ifdef HAVE_LSTAT
static int stat_cache_lstat(server *srv, buffer *dname, struct stat *lst) {
if (lstat(dname->ptr, lst) == 0) {
return S_ISLNK(lst->st_mode) ? 0 : 1;
}
else {
log_error_write(srv, __FILE__, __LINE__, "sbs",
"lstat failed for:",
dname, strerror(errno));
};
return -1;
}
#endif
/***
*
@ -604,7 +591,6 @@ handler_t stat_cache_get_entry(server *srv, connection *con, buffer *name, stat_
struct stat st;
int fd;
const int follow_symlink = con->conf.follow_symlink;
struct stat lst;
int file_ndx;
*ret_sce = NULL;
@ -716,54 +702,6 @@ handler_t stat_cache_get_entry(server *srv, connection *con, buffer *name, stat_
sce->st = st;
sce->stat_ts = srv->cur_ts;
/* catch the obvious symlinks
*
* this is not a secure check as we still have a race-condition between
* the stat() and the open. We can only solve this by
* 1. open() the file
* 2. fstat() the fd
*
* and keeping the file open for the rest of the time. But this can
* only be done at network level.
*
* per default it is not a symlink
* */
#ifdef HAVE_LSTAT
sce->is_symlink = 0;
/* we want to only check for symlinks if we should block symlinks.
*/
if (!follow_symlink) {
if (stat_cache_lstat(srv, name, &lst) == 0) {
sce->is_symlink = 1;
}
/*
* we assume "/" can not be symlink, so
* skip the symlink stuff if our path is /
**/
else if (buffer_string_length(name) > 1) {
buffer *dname;
char *s_cur;
dname = buffer_init();
buffer_copy_buffer(dname, name);
while ((s_cur = strrchr(dname->ptr, '/'))) {
buffer_string_set_length(dname, s_cur - dname->ptr);
if (dname->ptr == s_cur) {
break;
}
if (stat_cache_lstat(srv, dname, &lst) == 0) {
sce->is_symlink = 1;
break;
};
};
buffer_free(dname);
};
}
#endif
#ifdef HAVE_FAM_H
if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_FAM) {
stat_cache_fam_dir_monitor(srv, sc->scf, sce, name);
@ -775,6 +713,52 @@ handler_t stat_cache_get_entry(server *srv, connection *con, buffer *name, stat_
return HANDLER_GO_ON;
}
int stat_cache_path_contains_symlink(server *srv, buffer *name) {
/* caller should check for symlinks only if we should block symlinks. */
/* catch the obvious symlinks
*
* this is not a secure check as we still have a race-condition between
* the stat() and the open. We can only solve this by
* 1. open() the file
* 2. fstat() the fd
*
* and keeping the file open for the rest of the time. But this can
* only be done at network level.
* */
#ifdef HAVE_LSTAT
/* we assume "/" can not be symlink,
* so skip the symlink stuff if path is "/" */
size_t len = buffer_string_length(name);
force_assert(0 != len);
force_assert(name->ptr[0] == '/');
if (1 == len) return 0;
#ifndef PATH_MAX
#define PATH_MAX 4096
#endif
if (len >= PATH_MAX) return -1;
char buf[PATH_MAX];
memcpy(buf, name->ptr, len);
char *s_cur = buf+len;
do {
*s_cur = '\0';
struct stat st;
if (0 == lstat(buf, &st)) {
if (S_ISLNK(st.st_mode)) return 1;
}
else {
log_error_write(srv, __FILE__, __LINE__, "sss",
"lstat failed for:", buf, strerror(errno));
return -1;
}
} while ((s_cur = strrchr(buf, '/')) != buf);
#endif
return 0;
}
int stat_cache_open_rdonly_fstat (buffer *name, struct stat *st, int symlinks) {
/*(Note: O_NOFOLLOW affects only the final path segment, the target file,
* not any intermediate symlinks along the path)*/

5
src/stat_cache.h

@ -20,10 +20,6 @@ typedef struct {
time_t stat_ts;
#ifdef HAVE_LSTAT
char is_symlink;
#endif
#ifdef HAVE_FAM_H
int dir_version;
#endif
@ -44,6 +40,7 @@ const buffer * stat_cache_mimetype_by_ext(const connection *con, const char *nam
const buffer * stat_cache_content_type_get(server *srv, connection *con, const buffer *name, stat_cache_entry *sce);
const buffer * stat_cache_etag_get(stat_cache_entry *sce, etag_flags_t flags);
handler_t stat_cache_get_entry(server *srv, connection *con, buffer *name, stat_cache_entry **sce);
int stat_cache_path_contains_symlink(server *srv, buffer *name);
int stat_cache_open_rdonly_fstat (buffer *name, struct stat *st, int symlinks);
int stat_cache_trigger_cleanup(server *srv);

Loading…
Cancel
Save