[PATCH 1/2] Exportfs changes - When a export rootdir is present, nfsd_realpath() wrapper is used to avoid symlink exploits. - Removed canonicalization of rootdir paths. Export rootdir must now be an absolute path. - Implemented nfsd_path.h
From: Christopher Bii <hidden>
Date: 2024-12-05 02:04:25
Subsystem:
the rest · Maintainer:
Linus Torvalds
Signed-off-by: Christopher Bii <redacted> --- support/export/export.c | 24 +-- support/include/exportfs.h | 1 + support/include/nfsd_path.h | 9 +- support/misc/nfsd_path.c | 362 ++++++++++++------------------------ support/nfs/exports.c | 53 +++--- utils/exportfs/exportfs.c | 8 +- 6 files changed, 165 insertions(+), 292 deletions(-)
diff --git a/support/export/export.c b/support/export/export.c
index 2c8c3335..463ba846 100644
--- a/support/export/export.c
+++ b/support/export/export.c@@ -40,20 +40,7 @@ static int export_check(const nfs_export *exp, const struct addrinfo *ai,
static void
exportent_mkrealpath(struct exportent *eep)
{
- const char *chroot = nfsd_path_nfsd_rootdir();
- char *ret = NULL;
-
- if (chroot) {
- char buffer[PATH_MAX];
- if (realpath(chroot, buffer))
- ret = nfsd_path_prepend_dir(buffer, eep->e_path);
- else
- xlog(D_GENERAL, "%s: failed to resolve path %s: %m",
- __func__, chroot);
- }
- if (!ret)
- ret = xstrdup(eep->e_path);
- eep->e_realpath = ret;
+ eep->e_realpath = nfsd_path_prepend_root(eep->e_path);
}
char *@@ -64,6 +51,13 @@ exportent_realpath(struct exportent *eep) return eep->e_realpath; } +void +exportent_free_realpath(struct exportent* eep) +{ + if (eep->e_realpath && eep->e_realpath != eep->e_path) + free(eep->e_realpath); +}; + void exportent_release(struct exportent *eep) {
@@ -73,7 +67,7 @@ exportent_release(struct exportent *eep) free(eep->e_fslocdata); free(eep->e_uuid); xfree(eep->e_hostname); - xfree(eep->e_realpath); + exportent_free_realpath(eep); } static void
diff --git a/support/include/exportfs.h b/support/include/exportfs.h
index 9edf0d04..c65e2a1c 100644
--- a/support/include/exportfs.h
+++ b/support/include/exportfs.h@@ -173,6 +173,7 @@ struct export_features { struct export_features *get_export_features(void); void fix_pseudoflavor_flags(struct exportent *ep); +void exportent_free_realpath(struct exportent*); char *exportent_realpath(struct exportent *eep); int export_test(struct exportent *eep, int with_fsid); diff --git a/support/include/nfsd_path.h b/support/include/nfsd_path.h
index aa1e1dd0..9c5976e8 100644
--- a/support/include/nfsd_path.h
+++ b/support/include/nfsd_path.h@@ -8,12 +8,13 @@ struct file_handle; struct statfs; +struct nfsd_task_t; void nfsd_path_init(void); -const char * nfsd_path_nfsd_rootdir(void); +const char * nfsd_path_rootdir(void); char * nfsd_path_strip_root(char *pathname); -char * nfsd_path_prepend_dir(const char *dir, const char *pathname); +char * nfsd_path_prepend_root(char *pathname); int nfsd_path_stat(const char *pathname, struct stat *statbuf); int nfsd_path_lstat(const char *pathname, struct stat *statbuf);
@@ -23,8 +24,8 @@ int nfsd_path_statfs(const char *pathname, char * nfsd_realpath(const char *path, char *resolved_path); -ssize_t nfsd_path_read(int fd, char *buf, size_t len); -ssize_t nfsd_path_write(int fd, const char *buf, size_t len); +ssize_t nfsd_path_read(int fd, void* buf, size_t len); +ssize_t nfsd_path_write(int fd, void* buf, size_t len); int nfsd_name_to_handle_at(int fd, const char *path, struct file_handle *fh,
diff --git a/support/misc/nfsd_path.c b/support/misc/nfsd_path.c
index c3dea4f0..b896f091 100644
--- a/support/misc/nfsd_path.c
+++ b/support/misc/nfsd_path.c@@ -19,95 +19,79 @@ #include "nfsd_path.h" #include "workqueue.h" -static struct xthread_workqueue *nfsd_wq; +static struct xthread_workqueue *nfsd_wq = NULL; +const char *rootdir; +size_t rootdir_pathlen = 0; -static int -nfsd_path_isslash(const char *path) -{ - return path[0] == '/' && path[1] == '/'; -} +struct nfsd_task_t { + int ret; + void* data; +}; +/* Function used to offload tasks that must be ran within the correct + * chroot environment. + * */ +static void +nfsd_run_task(void (*func)(void*), void* data){ + nfsd_wq ? xthread_work_run_sync(nfsd_wq, func, data) : func(data); +}; -static int -nfsd_path_isdot(const char *path) +const char* +nfsd_path_rootdir(void) { - return path[0] == '.' && path[1] == '/'; -} + return rootdir; +}; -static const char * -nfsd_path_strip(const char *path) +/* Set rootdir global variable. Rootdir must be an absolute path + * and resolveable by realpath() + * */ +static void +nfsd_rootdir_set(void) { - if (!path || *path == '\0') - goto out; - for (;;) { - if (nfsd_path_isslash(path)) { - path++; - continue; - } - if (nfsd_path_isdot(path)) { - path += 2; - continue; - } - break; - } -out: - return path; -} + const char *path; + if (!(path = conf_get_str("exports", "rootdir"))) + return; -const char * -nfsd_path_nfsd_rootdir(void) -{ - const char *rootdir; - - rootdir = nfsd_path_strip(conf_get_str("exports", "rootdir")); - if (!rootdir || rootdir[0] == '\0') - return NULL; - if (rootdir[0] == '/' && rootdir[1] == '\0') - return NULL; - return rootdir; + if (path[0] != '/') + xlog(L_FATAL, "%s: NFS export rootdir must be an
absolute path. "
+ "Current value: \"%s\"", __func__, path);
+
+ if (!(rootdir = realpath(path, NULL))){
+ free((void*)rootdir);
+ xlog(L_FATAL, "realpath(): Unable to resolve root
export path \"%s\"", path);
+ };
+
+ rootdir_pathlen = strlen(rootdir);
}
char *
nfsd_path_strip_root(char *pathname)
{
- char buffer[PATH_MAX];
- const char *dir = nfsd_path_nfsd_rootdir();
-
- if (!dir)
- goto out;
-
- if (realpath(dir, buffer))
- return strstr(pathname, buffer) == pathname ?
- pathname + strlen(buffer) : NULL;
+ if (!rootdir)
+ return pathname;
- xlog(D_GENERAL, "%s: failed to resolve path %s: %m", __func__, dir);
-out:
- return pathname;
+ return strstr(pathname, rootdir) == pathname ?
+ pathname + rootdir_pathlen : pathname;
}
char *
-nfsd_path_prepend_dir(const char *dir, const char *pathname)
+nfsd_path_prepend_root(char *pathname)
{
- size_t len, dirlen;
- char *ret;
-
- dirlen = strlen(dir);
- while (dirlen > 0 && dir[dirlen - 1] == '/')
- dirlen--;
- if (!dirlen)
- return NULL;
- while (pathname[0] == '/')
- pathname++;
- len = dirlen + strlen(pathname) + 1;
- ret = xmalloc(len + 1);
- snprintf(ret, len+1, "%.*s/%s", (int)dirlen, dir, pathname);
- return ret;
+ char* buff;
+
+ if (!rootdir)
+ return pathname;
+
+ buff = malloc(strlen(pathname) + rootdir_pathlen + 1);
+ memcpy(buff, rootdir, rootdir_pathlen);
+ strcpy(buff + rootdir_pathlen, pathname);
+
+ return buff;
}
static void
nfsd_setup_workqueue(void)
{
- const char *rootdir = nfsd_path_nfsd_rootdir();
-
+ nfsd_rootdir_set();
if (!rootdir)
return;
@@ -124,224 +108,119 @@ nfsd_path_init(void)
}
struct nfsd_stat_data {
- const char *pathname;
- struct stat *statbuf;
- int ret;
- int err;
+ const char *pathname;
+ struct stat *statbuf;
+ int (*stat_handler)(const char*, struct stat*);
};
static void
-nfsd_statfunc(void *data)
-{
- struct nfsd_stat_data *d = data;
-
- d->ret = xstat(d->pathname, d->statbuf);
- if (d->ret < 0)
- d->err = errno;
-}
-
-static void
-nfsd_lstatfunc(void *data)
+nfsd_handle_stat(void *data)
{
- struct nfsd_stat_data *d = data;
-
- d->ret = xlstat(d->pathname, d->statbuf);
- if (d->ret < 0)
- d->err = errno;
+ struct nfsd_task_t* t = data;
+ struct nfsd_stat_data* d = t->data;
+ t->ret = d->stat_handler(d->pathname, d->statbuf);
}
static int
-nfsd_run_stat(struct xthread_workqueue *wq,
- void (*func)(void *),
- const char *pathname,
- struct stat *statbuf)
+nfsd_run_stat(const char *pathname,
+ struct stat *statbuf,
+ int (*handler)(const char*, struct stat*))
{
- struct nfsd_stat_data data = {
- pathname,
- statbuf,
- 0,
- 0
- };
- xthread_work_run_sync(wq, func, &data);
- if (data.ret < 0)
- errno = data.err;
- return data.ret;
+ struct nfsd_task_t t;
+ struct nfsd_stat_data d = { pathname, statbuf, handler };
+ t.data = &d;
+ nfsd_run_task(nfsd_handle_stat, &t);
+ return t.ret;
}
int
nfsd_path_stat(const char *pathname, struct stat *statbuf)
{
- if (!nfsd_wq)
- return xstat(pathname, statbuf);
- return nfsd_run_stat(nfsd_wq, nfsd_statfunc, pathname, statbuf);
+ return nfsd_run_stat(pathname, statbuf, stat);
}
int
-nfsd_path_lstat(const char *pathname, struct stat *statbuf)
-{
- if (!nfsd_wq)
- return xlstat(pathname, statbuf);
- return nfsd_run_stat(nfsd_wq, nfsd_lstatfunc, pathname, statbuf);
-}
-
-struct nfsd_statfs_data {
- const char *pathname;
- struct statfs *statbuf;
- int ret;
- int err;
+nfsd_path_lstat(const char* pathname, struct stat* statbuf){
+ return nfsd_run_stat(pathname, statbuf, lstat);
};
-static void
-nfsd_statfsfunc(void *data)
-{
- struct nfsd_statfs_data *d = data;
-
- d->ret = statfs(d->pathname, d->statbuf);
- if (d->ret < 0)
- d->err = errno;
-}
-
-static int
-nfsd_run_statfs(struct xthread_workqueue *wq,
- const char *pathname,
- struct statfs *statbuf)
-{
- struct nfsd_statfs_data data = {
- pathname,
- statbuf,
- 0,
- 0
- };
- xthread_work_run_sync(wq, nfsd_statfsfunc, &data);
- if (data.ret < 0)
- errno = data.err;
- return data.ret;
-}
-
int
-nfsd_path_statfs(const char *pathname, struct statfs *statbuf)
+nfsd_path_statfs(const char* pathname, struct statfs* statbuf)
{
- if (!nfsd_wq)
- return statfs(pathname, statbuf);
- return nfsd_run_statfs(nfsd_wq, pathname, statbuf);
-}
+ return nfsd_run_stat(pathname, (struct stat*)statbuf, (int
(*)(const char*, struct stat*))statfs);
+};
-struct nfsd_realpath_data {
- const char *pathname;
- char *resolved;
- int err;
+struct nfsd_realpath_t {
+ const char* path;
+ char* resolved_buf;
+ char* res_ptr;
};
static void
nfsd_realpathfunc(void *data)
{
- struct nfsd_realpath_data *d = data;
-
- d->resolved = realpath(d->pathname, d->resolved);
- if (!d->resolved)
- d->err = errno;
+ struct nfsd_realpath_t *d = data;
+ d->res_ptr = realpath(d->path, d->resolved_buf);
}
-char *
-nfsd_realpath(const char *path, char *resolved_path)
+char*
+nfsd_realpath(const char *path, char *resolved_buf)
{
- struct nfsd_realpath_data data = {
- path,
- resolved_path,
- 0
- };
-
- if (!nfsd_wq)
- return realpath(path, resolved_path);
-
- xthread_work_run_sync(nfsd_wq, nfsd_realpathfunc, &data);
- if (!data.resolved)
- errno = data.err;
- return data.resolved;
+ struct nfsd_realpath_t realpath_buf = {
+ .path = path,
+ .resolved_buf = resolved_buf
+ };
+ nfsd_run_task(nfsd_realpathfunc, &realpath_buf);
+ return realpath_buf.res_ptr;
}
-struct nfsd_read_data {
- int fd;
- char *buf;
- size_t len;
- ssize_t ret;
- int err;
+struct nfsd_rw_data {
+ int fd;
+ void* buf;
+ size_t len;
+ ssize_t bytes_read;
};
static void
nfsd_readfunc(void *data)
{
- struct nfsd_read_data *d = data;
-
- d->ret = read(d->fd, d->buf, d->len);
- if (d->ret < 0)
- d->err = errno;
+ struct nfsd_rw_data* t = (struct nfsd_rw_data*)data;
+ t->bytes_read = read(t->fd, t->buf, t->len);
}
static ssize_t
-nfsd_run_read(struct xthread_workqueue *wq, int fd, char *buf, size_t len)
+nfsd_run_read(int fd, void* buf, size_t len)
{
- struct nfsd_read_data data = {
- fd,
- buf,
- len,
- 0,
- 0
- };
- xthread_work_run_sync(wq, nfsd_readfunc, &data);
- if (data.ret < 0)
- errno = data.err;
- return data.ret;
+ struct nfsd_rw_data d = { .fd = fd, .buf = buf, .len = len };
+ nfsd_run_task(nfsd_readfunc, &d);
+ return d.bytes_read;
}
ssize_t
-nfsd_path_read(int fd, char *buf, size_t len)
+nfsd_path_read(int fd, void* buf, size_t len)
{
- if (!nfsd_wq)
- return read(fd, buf, len);
- return nfsd_run_read(nfsd_wq, fd, buf, len);
+ return nfsd_run_read(fd, buf, len);
}
-struct nfsd_write_data {
- int fd;
- const char *buf;
- size_t len;
- ssize_t ret;
- int err;
-};
-
static void
nfsd_writefunc(void *data)
{
- struct nfsd_write_data *d = data;
-
- d->ret = write(d->fd, d->buf, d->len);
- if (d->ret < 0)
- d->err = errno;
+ struct nfsd_rw_data* d = data;
+ d->bytes_read = write(d->fd, d->buf, d->len);
}
static ssize_t
-nfsd_run_write(struct xthread_workqueue *wq, int fd, const char *buf,
size_t len)
+nfsd_run_write(int fd, void* buf, size_t len)
{
- struct nfsd_write_data data = {
- fd,
- buf,
- len,
- 0,
- 0
- };
- xthread_work_run_sync(wq, nfsd_writefunc, &data);
- if (data.ret < 0)
- errno = data.err;
- return data.ret;
+ struct nfsd_rw_data d = { .fd = fd, .buf = buf, .len = len };
+ nfsd_run_task(nfsd_writefunc, &d);
+ return d.bytes_read;
}
ssize_t
-nfsd_path_write(int fd, const char *buf, size_t len)
+nfsd_path_write(int fd, void* buf, size_t len)
{
- if (!nfsd_wq)
- return write(fd, buf, len);
- return nfsd_run_write(nfsd_wq, fd, buf, len);
+ return nfsd_run_write(fd, buf, len);
}
#if defined(HAVE_NAME_TO_HANDLE_AT)@@ -352,23 +231,18 @@ struct nfsd_handle_data { int *mount_id; int flags; int ret; - int err; }; static void nfsd_name_to_handle_func(void *data) { struct nfsd_handle_data *d = data; - - d->ret = name_to_handle_at(d->fd, d->path, - d->fh, d->mount_id, d->flags); - if (d->ret < 0) - d->err = errno; + d->ret = name_to_handle_at(d->fd, d->path, d->fh, d->mount_id, d->flags); } static int -nfsd_run_name_to_handle_at(struct xthread_workqueue *wq, - int fd, const char *path, struct file_handle *fh, +nfsd_run_name_to_handle_at(int fd, const char *path, + struct file_handle *fh, int *mount_id, int flags) { struct nfsd_handle_data data = {
@@ -377,25 +251,19 @@ nfsd_run_name_to_handle_at(struct xthread_workqueue *wq,
fh,
mount_id,
flags,
- 0,
0
};
- xthread_work_run_sync(wq, nfsd_name_to_handle_func, &data);
- if (data.ret < 0)
- errno = data.err;
+ nfsd_run_task(nfsd_name_to_handle_func, &data);
return data.ret;
}
int
-nfsd_name_to_handle_at(int fd, const char *path, struct file_handle *fh,
+nfsd_name_to_handle_at(int fd, const char *path,
+ struct file_handle *fh,
int *mount_id, int flags)
{
- if (!nfsd_wq)
- return name_to_handle_at(fd, path, fh, mount_id, flags);
-
- return nfsd_run_name_to_handle_at(nfsd_wq, fd, path, fh,
- mount_id, flags);
+ return nfsd_run_name_to_handle_at(fd, path, fh, mount_id, flags);
}
#else
intdiff --git a/support/nfs/exports.c b/support/nfs/exports.c
index a6816e60..c8ce8566 100644
--- a/support/nfs/exports.c
+++ b/support/nfs/exports.c@@ -32,6 +32,7 @@ #include "xio.h" #include "pseudoflavors.h" #include "reexport.h" +#include "nfsd_path.h" #define EXPORT_DEFAULT_FLAGS \ (NFSEXP_READONLY|NFSEXP_ROOTSQUASH|NFSEXP_GATHERED_WRITES|NFSEXP_NOSUBTREECHECK)
@@ -160,7 +161,7 @@ getexportent(int fromkernel) } xfree(ee.e_hostname); - xfree(ee.e_realpath); + exportent_free_realpath(&ee); ee = def_ee; /* Check for default client */
@@ -185,28 +186,34 @@ getexportent(int fromkernel) } ee.e_hostname = xstrdup(hostname); - if (parseopts(opt, &ee, NULL) < 0) { - if(ee.e_hostname) - { - xfree(ee.e_hostname); - ee.e_hostname=NULL; - } - if(ee.e_uuid) - { - xfree(ee.e_uuid); - ee.e_uuid=NULL; - } + if (parseopts(opt, &ee, NULL) < 0) + goto out; - return NULL; - } /* resolve symlinks */ - if (realpath(ee.e_path, rpath) != NULL) { - rpath[sizeof (rpath) - 1] = '\0'; - strncpy(ee.e_path, rpath, sizeof (ee.e_path) - 1); - ee.e_path[sizeof (ee.e_path) - 1] = '\0'; - } + if (nfsd_realpath(ee.e_path, rpath) == NULL) { + xlog(L_ERROR, "realpath(): Unable to resolve path at
%s", ee.e_path);
+ goto out;
+ };
- return ⅇ
+ if (strlen(rpath) > sizeof(ee.e_path) - 1){
+ xlog(L_ERROR, "Path %s exceeds limit of %lu chars",
ee.e_path, sizeof(ee.e_path) - 1);
+ goto out;
+ };
+
+ strcpy(ee.e_path, rpath);
+ return ⅇ
+
+out:
+ if (ee.e_hostname){
+ free(ee.e_hostname);
+ ee.e_hostname = NULL;
+ };
+ if (ee.e_uuid){
+ free(ee.e_uuid);
+ ee.e_uuid = NULL;
+ };
+
+ return NULL;
}
static const struct secinfo_flag_displaymap {@@ -424,15 +431,15 @@ mkexportent(char *hname, char *path, char *options) xfree(ee.e_hostname); ee.e_hostname = xstrdup(hname); - xfree(ee.e_realpath); + exportent_free_realpath(&ee); ee.e_realpath = NULL; if (strlen(path) >= sizeof(ee.e_path)) { xlog(L_ERROR, "path name %s too long", path); return NULL; } - strncpy(ee.e_path, path, sizeof (ee.e_path)); - ee.e_path[sizeof (ee.e_path) - 1] = '\0'; + strcpy(ee.e_path, path); + if (parseopts(options, &ee, NULL) < 0) return NULL; return ⅇ
diff --git a/utils/exportfs/exportfs.c b/utils/exportfs/exportfs.c
index b03a047b..0697e398 100644
--- a/utils/exportfs/exportfs.c
+++ b/utils/exportfs/exportfs.c@@ -266,7 +266,6 @@ export_all(int verbose) } } - static void exportfs_parsed(char *hname, char *path, char *options, int verbose) {
@@ -512,11 +511,13 @@ validate_export(nfs_export *exp) * If a path doesn't exist, or is not a dir or file, give an warning * otherwise trial-export to '-test-client-' and check for failure. */ - struct stat stb; - char *path = exportent_realpath(&exp->m_export); + struct stat stb; + char *path; struct statfs stf; int fs_has_fsid = 0; + path = exportent_realpath(&exp->m_export); + if (stat(path, &stb) < 0) { xlog(L_ERROR, "Failed to stat %s: %m", path); return;
@@ -547,6 +548,7 @@ validate_export(nfs_export *exp) return; } + } static _Bool
--
2.47.1