From: Jérémie Galarneau Date: Wed, 17 Apr 2019 20:55:27 +0000 (-0400) Subject: Add mkdirat utils and runas wrappers X-Git-Tag: v2.11.0-rc2~27 X-Git-Url: https://git.lttng.org./?a=commitdiff_plain;h=a42089bd2788e4eeb14527c53d9d620fc33141df;p=lttng-tools.git Add mkdirat utils and runas wrappers The lttng_directory_handle allows its user to keep a handle to a directory and to create subdirectories relative to it. On platforms implementing POSIX.2008, a directory file descriptor is used to maintain a handle to an existing directory and used in conjunction with mkdirat() to create subdirectories. Derelict platforms (such as Solaris 10) use an alternative implementation which carries the location of the root directory and builds subdirectory paths on creation. The existing mkdir utils are re-implemented using this new interface (using the special AT_FDCWD file descriptor value, when applicable) to limit code duplication. The implementation of the directory handle and its users is automatically selected based on the presence of the dirfd() function, but can also be explicitly chosen using the --enable/disable-dirfd configuration option. Signed-off-by: Jérémie Galarneau --- diff --git a/configure.ac b/configure.ac index 67214731e..06e6e10e6 100644 --- a/configure.ac +++ b/configure.ac @@ -639,6 +639,17 @@ AX_CONFIG_FEATURE( ) AM_CONDITIONAL([COMPAT_EPOLL], [ test "$enable_epoll" = "yes" ]) +AS_IF([test "x$ac_cv_func_dirfd" = "xyes"], + [AX_CONFIG_FEATURE_ENABLE(dirfd)], + [AX_CONFIG_FEATURE_DISABLE(dirfd)] +) +AX_CONFIG_FEATURE( + [dirfd], [Use directory file descriptors], + [COMPAT_DIRFD], [This platform supports directory file descriptors.], + [enable_dirfd="yes"], [enable_dirfd="no"] +) +AM_CONDITIONAL([COMPAT_DIRFD], [ test "$enable_dirfd" = "yes" ]) + AM_CONDITIONAL([TEST_JAVA_JUL_AGENT], [test "x$test_java_agent_jul" = "xyes"]) AM_CONDITIONAL([TEST_JAVA_LOG4J_AGENT], [test "x$test_java_agent_log4j" = "xyes"]) diff --git a/src/common/Makefile.am b/src/common/Makefile.am index dae067df6..3e816b26a 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -28,7 +28,7 @@ libcommon_la_SOURCES = error.h error.c utils.c utils.h runas.h runas.c \ location.c \ waiter.h waiter.c \ userspace-probe.c event.c time.c \ - session-descriptor.c + session-descriptor.c credentials.h if HAVE_ELF_H libcommon_la_SOURCES += lttng-elf.h lttng-elf.c @@ -36,6 +36,7 @@ endif libcommon_la_LIBADD = \ $(top_builddir)/src/common/config/libconfig.la \ + $(top_builddir)/src/common/compat/libcompat.la \ $(UUID_LIBS) if BUILD_LIB_COMPAT diff --git a/src/common/compat/Makefile.am b/src/common/compat/Makefile.am index c5418baeb..b31158f0d 100644 --- a/src/common/compat/Makefile.am +++ b/src/common/compat/Makefile.am @@ -9,4 +9,4 @@ endif libcompat_la_SOURCES = poll.h fcntl.h endian.h mman.h dirent.h \ socket.h compat-fcntl.c uuid.h tid.h \ getenv.h string.h prctl.h paths.h netdb.h $(COMPAT) \ - time.h + time.h directory-handle.h directory-handle.c diff --git a/src/common/compat/directory-handle.c b/src/common/compat/directory-handle.c new file mode 100644 index 000000000..7077378e0 --- /dev/null +++ b/src/common/compat/directory-handle.c @@ -0,0 +1,475 @@ +/* + * Copyright (C) 2019 - Jérémie Galarneau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2 only, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +static +int lttng_directory_handle_stat(const struct lttng_directory_handle *handle, + const char *path, struct stat *st); +static +int lttng_directory_handle_mkdir( + const struct lttng_directory_handle *handle, + const char *path, mode_t mode); +static +int _run_as_mkdir(const struct lttng_directory_handle *handle, const char *path, + mode_t mode, uid_t uid, gid_t gid); +static +int _run_as_mkdir_recursive(const struct lttng_directory_handle *handle, + const char *path, mode_t mode, uid_t uid, gid_t gid); + +#ifdef COMPAT_DIRFD + +LTTNG_HIDDEN +int lttng_directory_handle_init(struct lttng_directory_handle *handle, + const char *path) +{ + int ret; + + if (!path) { + handle->dirfd = AT_FDCWD; + ret = 0; + goto end; + } + ret = open(path, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + if (ret == -1) { + PERROR("Failed to initialize directory handle to \"%s\"", path); + goto end; + } + handle->dirfd = ret; + ret = 0; +end: + return ret; +} + +LTTNG_HIDDEN +int lttng_directory_handle_init_from_dirfd( + struct lttng_directory_handle *handle, int dirfd) +{ + handle->dirfd = dirfd; + return 0; +} + +LTTNG_HIDDEN +void lttng_directory_handle_fini(struct lttng_directory_handle *handle) +{ + int ret; + + if (handle->dirfd == AT_FDCWD) { + return; + } + ret = close(handle->dirfd); + if (ret == -1) { + PERROR("Failed to close directory file descriptor of directory handle"); + } +} + +static +int lttng_directory_handle_stat(const struct lttng_directory_handle *handle, + const char *path, struct stat *st) +{ + return fstatat(handle->dirfd, path, st, 0); +} + +static +int lttng_directory_handle_mkdir( + const struct lttng_directory_handle *handle, + const char *path, mode_t mode) +{ + return mkdirat(handle->dirfd, path, mode); +} + +static +int _run_as_mkdir(const struct lttng_directory_handle *handle, const char *path, + mode_t mode, uid_t uid, gid_t gid) +{ + return run_as_mkdirat(handle->dirfd, path, mode, uid, gid); +} + +static +int _run_as_mkdir_recursive(const struct lttng_directory_handle *handle, + const char *path, mode_t mode, uid_t uid, gid_t gid) +{ + return run_as_mkdirat_recursive(handle->dirfd, path, mode, uid, gid); +} + +#else /* COMPAT_DIRFD */ + +LTTNG_HIDDEN +int lttng_directory_handle_init(struct lttng_directory_handle *handle, + const char *path) +{ + int ret; + size_t cwd_len, path_len, handle_path_len; + char cwd_buf[LTTNG_PATH_MAX]; + const char *cwd; + bool add_slash = false; + struct stat stat_buf; + + cwd = getcwd(cwd_buf, sizeof(cwd_buf)); + if (!cwd) { + PERROR("Failed to initialize directory handle, can't get current working directory"); + ret = -1; + goto end; + } + cwd_len = strlen(cwd); + if (cwd_len == 0) { + ERR("Failed to initialize directory handle to \"%s\": getcwd() returned an empty string", + path); + ret = -1; + goto end; + } + if (cwd[cwd_len - 1] != '/') { + add_slash = true; + } + + if (path) { + path_len = strlen(path); + if (path_len == 0) { + ERR("Failed to initialize directory handle: provided path is an empty string"); + ret = -1; + goto end; + } + + /* + * Ensure that 'path' is a directory. There is a race + * (TOCTOU) since the directory could be removed/replaced/renamed, + * but this is inevitable on platforms that don't provide dirfd support. + */ + ret = stat(path, &stat_buf); + if (ret == -1) { + PERROR("Failed to initialize directory handle to \"%s\", stat() failed", + path); + goto end; + } + if (!S_ISDIR(stat_buf.st_mode)) { + ERR("Failed to initialize directory handle to \"%s\": not a directory", + path); + ret = -1; + goto end; + } + if (*path == '/') { + handle->base_path = strdup(path); + if (!handle->base_path) { + ret = -1; + } + /* Not an error. */ + goto end; + } + } else { + path = ""; + path_len = 0; + add_slash = false; + } + + handle_path_len = cwd_len + path_len + !!add_slash + 2; + if (handle_path_len >= LTTNG_PATH_MAX) { + ERR("Failed to initialize directory handle as the resulting path's length (%zu bytes) exceeds the maximal allowed length (%d bytes)", + handle_path_len, LTTNG_PATH_MAX); + ret = -1; + goto end; + } + handle->base_path = zmalloc(handle_path_len); + if (!handle->base_path) { + PERROR("Failed to initialize directory handle"); + ret = -1; + goto end; + } + + ret = sprintf(handle->base_path, "%s%s%s/", cwd, + add_slash ? "/" : "", path); + if (ret == -1 || ret >= handle_path_len) { + ERR("Failed to initialize directory handle: path formatting failed"); + ret = -1; + goto end; + } +end: + return ret; +} + +LTTNG_HIDDEN +int lttng_directory_handle_init_from_dirfd( + struct lttng_directory_handle *handle, int dirfd) +{ + assert(dirfd == AT_FDCWD); + return lttng_directory_handle_init(handle, NULL); +} + +LTTNG_HIDDEN +void lttng_directory_handle_fini(struct lttng_directory_handle *handle) +{ + free(handle->base_path); +} + +static +int get_full_path(const struct lttng_directory_handle *handle, + const char *subdirectory, char *fullpath, size_t size) +{ + int ret; + + /* + * Don't include the base path if subdirectory is absolute. + * This is the same behaviour than mkdirat. + */ + ret = snprintf(fullpath, size, "%s%s", + *subdirectory != '/' ? handle->base_path : "", + subdirectory); + if (ret == -1 || ret >= size) { + ERR("Failed to format subdirectory from directory handle"); + ret = -1; + } + ret = 0; + return ret; +} + +static +int lttng_directory_handle_stat(const struct lttng_directory_handle *handle, + const char *subdirectory, struct stat *st) +{ + int ret; + char fullpath[LTTNG_PATH_MAX]; + + ret = get_full_path(handle, subdirectory, fullpath, sizeof(fullpath)); + if (ret) { + errno = ENOMEM; + goto end; + } + + ret = stat(fullpath, st); +end: + return ret; +} + +static +int lttng_directory_handle_mkdir(const struct lttng_directory_handle *handle, + const char *subdirectory, mode_t mode) +{ + int ret; + char fullpath[LTTNG_PATH_MAX]; + + ret = get_full_path(handle, subdirectory, fullpath, sizeof(fullpath)); + if (ret) { + errno = ENOMEM; + goto end; + } + + ret = mkdir(fullpath, mode); +end: + return ret; +} + +static +int _run_as_mkdir(const struct lttng_directory_handle *handle, const char *path, + mode_t mode, uid_t uid, gid_t gid) +{ + int ret; + char fullpath[LTTNG_PATH_MAX]; + + ret = get_full_path(handle, path, fullpath, sizeof(fullpath)); + if (ret) { + errno = ENOMEM; + goto end; + } + + ret = run_as_mkdir(fullpath, mode, uid, gid); +end: + return ret; +} + +static +int _run_as_mkdir_recursive(const struct lttng_directory_handle *handle, + const char *path, mode_t mode, uid_t uid, gid_t gid) +{ + int ret; + char fullpath[LTTNG_PATH_MAX]; + + ret = get_full_path(handle, path, fullpath, sizeof(fullpath)); + if (ret) { + errno = ENOMEM; + goto end; + } + + ret = run_as_mkdir_recursive(fullpath, mode, uid, gid); +end: + return ret; +} + +#endif /* COMPAT_DIRFD */ + +/* + * On some filesystems (e.g. nfs), mkdir will validate access rights before + * checking for the existence of the path element. This means that on a setup + * where "/home/" is a mounted NFS share, and running as an unpriviledged user, + * recursively creating a path of the form "/home/my_user/trace/" will fail with + * EACCES on mkdir("/home", ...). + * + * Checking the path for existence allows us to work around this behaviour. + */ +static +int create_directory_check_exists(const struct lttng_directory_handle *handle, + const char *path, mode_t mode) +{ + int ret = 0; + struct stat st; + + ret = lttng_directory_handle_stat(handle, path, &st); + if (ret == 0) { + if (S_ISDIR(st.st_mode)) { + /* Directory exists, skip. */ + goto end; + } else { + /* Exists, but is not a directory. */ + errno = ENOTDIR; + ret = -1; + goto end; + } + } + + /* + * Let mkdir handle other errors as the caller expects mkdir + * semantics. + */ + ret = lttng_directory_handle_mkdir(handle, path, mode); +end: + return ret; +} + +/* Common implementation. */ +static +int create_directory_recursive(const struct lttng_directory_handle *handle, + const char *path, mode_t mode) +{ + char *p, tmp[LTTNG_PATH_MAX]; + size_t len; + int ret; + + assert(path); + + ret = lttng_strncpy(tmp, path, sizeof(tmp)); + if (ret) { + ERR("Failed to create directory: provided path's length (%zu bytes) exceeds the maximal allowed length (%zu bytes)", + strlen(path) + 1, sizeof(tmp)); + goto error; + } + + len = strlen(path); + if (tmp[len - 1] == '/') { + tmp[len - 1] = 0; + } + + for (p = tmp + 1; *p; p++) { + if (*p == '/') { + *p = 0; + if (tmp[strlen(tmp) - 1] == '.' && + tmp[strlen(tmp) - 2] == '.' && + tmp[strlen(tmp) - 3] == '/') { + ERR("Using '/../' is not permitted in the trace path (%s)", + tmp); + ret = -1; + goto error; + } + ret = create_directory_check_exists(handle, tmp, mode); + if (ret < 0) { + if (errno != EACCES) { + PERROR("Failed to create directory \"%s\"", + path); + ret = -errno; + goto error; + } + } + *p = '/'; + } + } + + ret = create_directory_check_exists(handle, tmp, mode); + if (ret < 0) { + PERROR("mkdirat recursive last element"); + ret = -errno; + } +error: + return ret; +} + +LTTNG_HIDDEN +int lttng_directory_handle_create_subdirectory_as_user( + const struct lttng_directory_handle *handle, + const char *subdirectory, + mode_t mode, struct lttng_credentials *creds) +{ + int ret; + + if (!creds) { + /* Run as current user. */ + ret = create_directory_check_exists(handle, + subdirectory, mode); + } else { + ret = _run_as_mkdir(handle, subdirectory, + mode, creds->uid, creds->gid); + } + + return ret; +} + +LTTNG_HIDDEN +int lttng_directory_handle_create_subdirectory_recursive_as_user( + const struct lttng_directory_handle *handle, + const char *subdirectory_path, + mode_t mode, struct lttng_credentials *creds) +{ + int ret; + + if (!creds) { + /* Run as current user. */ + ret = create_directory_recursive(handle, + subdirectory_path, mode); + } else { + ret = _run_as_mkdir_recursive(handle, subdirectory_path, + mode, creds->uid, creds->gid); + } + + return ret; +} + +LTTNG_HIDDEN +int lttng_directory_handle_create_subdirectory( + const struct lttng_directory_handle *handle, + const char *subdirectory, + mode_t mode) +{ + return lttng_directory_handle_create_subdirectory_as_user( + handle, subdirectory, mode, NULL); +} + +LTTNG_HIDDEN +int lttng_directory_handle_create_subdirectory_recursive( + const struct lttng_directory_handle *handle, + const char *subdirectory_path, + mode_t mode) +{ + return lttng_directory_handle_create_subdirectory_recursive_as_user( + handle, subdirectory_path, mode, NULL); +} diff --git a/src/common/compat/directory-handle.h b/src/common/compat/directory-handle.h new file mode 100644 index 000000000..a24bbd8db --- /dev/null +++ b/src/common/compat/directory-handle.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2019 - Jérémie Galarneau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2 only, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _COMPAT_DIRECTORY_HANDLE_H +#define _COMPAT_DIRECTORY_HANDLE_H + +#include +#include + +/* + * Some platforms, such as Solaris 10, do not support directory file descriptors + * and their associated functions (*at(...)), which are defined in POSIX.2008. + * + * This wrapper provides a handle that is either a copy of a directory's path + * or a directory file descriptors, depending on the platform's capabilities. + */ +#ifdef COMPAT_DIRFD +struct lttng_directory_handle { + int dirfd; +}; +#else +struct lttng_directory_handle { + char *base_path; +}; +#endif + +/* + * Initialize a directory handle to the provided path. Passing a NULL path + * returns a handle to the current working directory. The working directory + * is not sampled; it will be accessed at the time of use of the functions + * of this API. + * + * An initialized directory handle must be finalized using + * lttng_directory_handle_fini(). + */ +LTTNG_HIDDEN +int lttng_directory_handle_init(struct lttng_directory_handle *handle, + const char *path); + +LTTNG_HIDDEN +int lttng_directory_handle_init_from_dirfd( + struct lttng_directory_handle *handle, int dirfd); + +/* + * Release the resources of a directory handle. + */ +LTTNG_HIDDEN +void lttng_directory_handle_fini(struct lttng_directory_handle *handle); + +/* + * Create a subdirectory relative to a directory handle. + */ +LTTNG_HIDDEN +int lttng_directory_handle_create_subdirectory( + const struct lttng_directory_handle *handle, + const char *subdirectory, + mode_t mode); + +/* + * Create a subdirectory relative to a directory handle + * as a given user. + */ +LTTNG_HIDDEN +int lttng_directory_handle_create_subdirectory_as_user( + const struct lttng_directory_handle *handle, + const char *subdirectory, + mode_t mode, struct lttng_credentials *creds); + +/* + * Recursively create a directory relative to a directory handle. + */ +LTTNG_HIDDEN +int lttng_directory_handle_create_subdirectory_recursive( + const struct lttng_directory_handle *handle, + const char *subdirectory_path, + mode_t mode); + +/* + * Recursively create a directory relative to a directory handle + * as a given user. + */ +LTTNG_HIDDEN +int lttng_directory_handle_create_subdirectory_recursive_as_user( + const struct lttng_directory_handle *handle, + const char *subdirectory_path, + mode_t mode, struct lttng_credentials *creds); + +#endif /* _COMPAT_PATH_HANDLE_H */ diff --git a/src/common/credentials.h b/src/common/credentials.h new file mode 100644 index 000000000..11aeba9c7 --- /dev/null +++ b/src/common/credentials.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 - Jérémie Galarneau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2 only, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef LTTNG_CREDENTIALS_H +#define LTTNG_CREDENTIALS_H + +#include + +struct lttng_credentials { + uid_t uid; + gid_t gid; +}; + +#endif /* LTTNG_CREDENTIALS_H */ diff --git a/src/common/runas.c b/src/common/runas.c index c5cf09bfc..43b6704b7 100644 --- a/src/common/runas.c +++ b/src/common/runas.c @@ -49,7 +49,7 @@ struct run_as_data; struct run_as_ret; typedef int (*run_as_fct)(struct run_as_data *data, struct run_as_ret *ret_value); -struct run_as_mkdir_data { +struct run_as_mkdirat_data { char path[PATH_MAX]; mode_t mode; }; @@ -77,7 +77,7 @@ struct run_as_extract_sdt_probe_offsets_data { char provider_name[LTTNG_SYMBOL_NAME_LEN]; }; -struct run_as_mkdir_ret { +struct run_as_mkdirat_ret { int ret; }; @@ -104,10 +104,12 @@ struct run_as_extract_sdt_probe_offsets_ret { enum run_as_cmd { RUN_AS_MKDIR, + RUN_AS_MKDIRAT, + RUN_AS_MKDIR_RECURSIVE, + RUN_AS_MKDIRAT_RECURSIVE, RUN_AS_OPEN, RUN_AS_UNLINK, RUN_AS_RMDIR_RECURSIVE, - RUN_AS_MKDIR_RECURSIVE, RUN_AS_EXTRACT_ELF_SYMBOL_OFFSET, RUN_AS_EXTRACT_SDT_PROBE_OFFSETS, }; @@ -116,7 +118,7 @@ struct run_as_data { enum run_as_cmd cmd; int fd; union { - struct run_as_mkdir_data mkdir; + struct run_as_mkdirat_data mkdirat; struct run_as_open_data open; struct run_as_unlink_data unlink; struct run_as_rmdir_recursive_data rmdir_recursive; @@ -145,7 +147,7 @@ struct run_as_data { struct run_as_ret { int fd; union { - struct run_as_mkdir_ret mkdir; + struct run_as_mkdirat_ret mkdirat; struct run_as_open_ret open; struct run_as_unlink_ret unlink; struct run_as_rmdir_recursive_ret rmdir_recursive; @@ -181,35 +183,49 @@ int use_clone(void) } #endif -LTTNG_HIDDEN -int _utils_mkdir_recursive_unsafe(const char *path, mode_t mode); - /* * Create recursively directory using the FULL path. */ static -int _mkdir_recursive(struct run_as_data *data, struct run_as_ret *ret_value) +int _mkdirat_recursive(struct run_as_data *data, struct run_as_ret *ret_value) { const char *path; mode_t mode; + struct lttng_directory_handle handle; - path = data->u.mkdir.path; - mode = data->u.mkdir.mode; + path = data->u.mkdirat.path; + mode = data->u.mkdirat.mode; + (void) lttng_directory_handle_init_from_dirfd(&handle, data->fd); /* Safe to call as we have transitioned to the requested uid/gid. */ - ret_value->u.mkdir.ret = _utils_mkdir_recursive_unsafe(path, mode); + ret_value->u.mkdirat.ret = + lttng_directory_handle_create_subdirectory_recursive( + &handle, path, mode); ret_value->_errno = errno; - ret_value->_error = (ret_value->u.mkdir.ret) ? true : false; - return ret_value->u.mkdir.ret; + ret_value->_error = (ret_value->u.mkdirat.ret) ? true : false; + lttng_directory_handle_fini(&handle); + return ret_value->u.mkdirat.ret; } static -int _mkdir(struct run_as_data *data, struct run_as_ret *ret_value) +int _mkdirat(struct run_as_data *data, struct run_as_ret *ret_value) { - ret_value->u.mkdir.ret = mkdir(data->u.mkdir.path, data->u.mkdir.mode); + const char *path; + mode_t mode; + struct lttng_directory_handle handle; + + path = data->u.mkdirat.path; + mode = data->u.mkdirat.mode; + + (void) lttng_directory_handle_init_from_dirfd(&handle, data->fd); + /* Safe to call as we have transitioned to the requested uid/gid. */ + ret_value->u.mkdirat.ret = + lttng_directory_handle_create_subdirectory( + &handle, path, mode); ret_value->_errno = errno; - ret_value->_error = (ret_value->u.mkdir.ret) ? true : false; - return ret_value->u.mkdir.ret; + ret_value->_error = (ret_value->u.mkdirat.ret) ? true : false; + lttng_directory_handle_fini(&handle); + return ret_value->u.mkdirat.ret; } static @@ -322,15 +338,17 @@ run_as_fct run_as_enum_to_fct(enum run_as_cmd cmd) { switch (cmd) { case RUN_AS_MKDIR: - return _mkdir; + case RUN_AS_MKDIRAT: + return _mkdirat; + case RUN_AS_MKDIR_RECURSIVE: + case RUN_AS_MKDIRAT_RECURSIVE: + return _mkdirat_recursive; case RUN_AS_OPEN: return _open; case RUN_AS_UNLINK: return _unlink; case RUN_AS_RMDIR_RECURSIVE: return _rmdir_recursive; - case RUN_AS_MKDIR_RECURSIVE: - return _mkdir_recursive; case RUN_AS_EXTRACT_ELF_SYMBOL_OFFSET: return _extract_elf_symbol_offset; case RUN_AS_EXTRACT_SDT_PROBE_OFFSETS: @@ -390,6 +408,8 @@ int send_fd_to_worker(struct run_as_worker *worker, enum run_as_cmd cmd, int fd) switch (cmd) { case RUN_AS_EXTRACT_ELF_SYMBOL_OFFSET: case RUN_AS_EXTRACT_SDT_PROBE_OFFSETS: + case RUN_AS_MKDIRAT: + case RUN_AS_MKDIRAT_RECURSIVE: break; default: return 0; @@ -468,7 +488,13 @@ int recv_fd_from_master(struct run_as_worker *worker, enum run_as_cmd cmd, int * switch (cmd) { case RUN_AS_EXTRACT_ELF_SYMBOL_OFFSET: case RUN_AS_EXTRACT_SDT_PROBE_OFFSETS: + case RUN_AS_MKDIRAT: + case RUN_AS_MKDIRAT_RECURSIVE: break; + case RUN_AS_MKDIR: + case RUN_AS_MKDIR_RECURSIVE: + *fd = AT_FDCWD; + /* fall-through */ default: return 0; } @@ -490,6 +516,8 @@ int cleanup_received_fd(enum run_as_cmd cmd, int fd) switch (cmd) { case RUN_AS_EXTRACT_ELF_SYMBOL_OFFSET: case RUN_AS_EXTRACT_SDT_PROBE_OFFSETS: + case RUN_AS_MKDIRAT: + case RUN_AS_MKDIRAT_RECURSIVE: break; default: return 0; @@ -1102,39 +1130,74 @@ err: LTTNG_HIDDEN int run_as_mkdir_recursive(const char *path, mode_t mode, uid_t uid, gid_t gid) { + return run_as_mkdirat_recursive(AT_FDCWD, path, mode, uid, gid); +} + +LTTNG_HIDDEN +int run_as_mkdirat_recursive(int dirfd, const char *path, mode_t mode, + uid_t uid, gid_t gid) +{ + int ret; struct run_as_data data; - struct run_as_ret ret; + struct run_as_ret run_as_ret; memset(&data, 0, sizeof(data)); - memset(&ret, 0, sizeof(ret)); - DBG3("mkdir() recursive %s with mode %d for uid %d and gid %d", + memset(&run_as_ret, 0, sizeof(run_as_ret)); + DBG3("mkdirat() recursive fd = %d%s, path = %s, mode = %d, uid = %d, gid = %d", + dirfd, dirfd == AT_FDCWD ? " (AT_FDCWD)" : "", path, (int) mode, (int) uid, (int) gid); - strncpy(data.u.mkdir.path, path, PATH_MAX - 1); - data.u.mkdir.path[PATH_MAX - 1] = '\0'; - data.u.mkdir.mode = mode; - - run_as(RUN_AS_MKDIR_RECURSIVE, &data, &ret, uid, gid); - errno = ret._errno; - return ret.u.mkdir.ret; + ret = lttng_strncpy(data.u.mkdirat.path, path, + sizeof(data.u.mkdirat.path)); + if (ret) { + ERR("Failed to copy path argument of mkdirat recursive command"); + goto error; + } + data.u.mkdirat.path[PATH_MAX - 1] = '\0'; + data.u.mkdirat.mode = mode; + data.fd = dirfd; + run_as(dirfd == AT_FDCWD ? RUN_AS_MKDIR_RECURSIVE : RUN_AS_MKDIRAT_RECURSIVE, + &data, &run_as_ret, uid, gid); + errno = run_as_ret._errno; + ret = run_as_ret.u.mkdirat.ret; +error: + return ret; } LTTNG_HIDDEN int run_as_mkdir(const char *path, mode_t mode, uid_t uid, gid_t gid) { + return run_as_mkdirat(AT_FDCWD, path, mode, uid, gid); +} + +LTTNG_HIDDEN +int run_as_mkdirat(int dirfd, const char *path, mode_t mode, + uid_t uid, gid_t gid) +{ + int ret; struct run_as_data data; - struct run_as_ret ret; + struct run_as_ret run_as_ret; memset(&data, 0, sizeof(data)); - memset(&ret, 0, sizeof(ret)); + memset(&run_as_ret, 0, sizeof(run_as_ret)); - DBG3("mkdir() %s with mode %d for uid %d and gid %d", + DBG3("mkdirat() recursive fd = %d%s, path = %s, mode = %d, uid = %d, gid = %d", + dirfd, dirfd == AT_FDCWD ? " (AT_FDCWD)" : "", path, (int) mode, (int) uid, (int) gid); - strncpy(data.u.mkdir.path, path, PATH_MAX - 1); - data.u.mkdir.path[PATH_MAX - 1] = '\0'; - data.u.mkdir.mode = mode; - run_as(RUN_AS_MKDIR, &data, &ret, uid, gid); - errno = ret._errno; - return ret.u.mkdir.ret; + ret = lttng_strncpy(data.u.mkdirat.path, path, + sizeof(data.u.mkdirat.path)); + if (ret) { + ERR("Failed to copy path argument of mkdirat command"); + goto error; + } + data.u.mkdirat.path[PATH_MAX - 1] = '\0'; + data.u.mkdirat.mode = mode; + data.fd = dirfd; + run_as(dirfd == AT_FDCWD ? RUN_AS_MKDIR : RUN_AS_MKDIRAT, + &data, &run_as_ret, uid, gid); + errno = run_as_ret._errno; + ret = run_as_ret._errno; +error: + return ret; } LTTNG_HIDDEN diff --git a/src/common/runas.h b/src/common/runas.h index 4d5639c4b..827371774 100644 --- a/src/common/runas.h +++ b/src/common/runas.h @@ -41,8 +41,14 @@ typedef int (*post_fork_cleanup_cb)(void *user_data); LTTNG_HIDDEN int run_as_mkdir_recursive(const char *path, mode_t mode, uid_t uid, gid_t gid); LTTNG_HIDDEN +int run_as_mkdirat_recursive(int dirfd, const char *path, mode_t mode, + uid_t uid, gid_t gid); +LTTNG_HIDDEN int run_as_mkdir(const char *path, mode_t mode, uid_t uid, gid_t gid); LTTNG_HIDDEN +int run_as_mkdirat(int dirfd, const char *path, mode_t mode, + uid_t uid, gid_t gid); +LTTNG_HIDDEN int run_as_open(const char *path, int flags, mode_t mode, uid_t uid, gid_t gid); LTTNG_HIDDEN int run_as_unlink(const char *path, uid_t uid, gid_t gid); diff --git a/src/common/utils.c b/src/common/utils.c index 4c000e9b4..5be67c11f 100644 --- a/src/common/utils.c +++ b/src/common/utils.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include "utils.h" @@ -667,44 +668,6 @@ error: return fd; } -/* - * On some filesystems (e.g. nfs), mkdir will validate access rights before - * checking for the existence of the path element. This means that on a setup - * where "/home/" is a mounted NFS share, and running as an unpriviledged user, - * recursively creating a path of the form "/home/my_user/trace/" will fail with - * EACCES on mkdir("/home", ...). - * - * Performing a stat(...) on the path to check for existence allows us to - * work around this behaviour. - */ -static -int mkdir_check_exists(const char *path, mode_t mode) -{ - int ret = 0; - struct stat st; - - ret = stat(path, &st); - if (ret == 0) { - if (S_ISDIR(st.st_mode)) { - /* Directory exists, skip. */ - goto end; - } else { - /* Exists, but is not a directory. */ - errno = ENOTDIR; - ret = -1; - goto end; - } - } - - /* - * Let mkdir handle other errors as the caller expects mkdir - * semantics. - */ - ret = mkdir(path, mode); -end: - return ret; -} - /* * Create directory using the given path and mode. * @@ -714,82 +677,17 @@ LTTNG_HIDDEN int utils_mkdir(const char *path, mode_t mode, int uid, int gid) { int ret; - - if (uid < 0 || gid < 0) { - ret = mkdir_check_exists(path, mode); - } else { - ret = run_as_mkdir(path, mode, uid, gid); - } - if (ret < 0) { - if (errno != EEXIST) { - PERROR("mkdir %s, uid %d, gid %d", path ? path : "NULL", - uid, gid); - } else { - ret = 0; - } - } - - return ret; -} - -/* - * Internal version of mkdir_recursive. Runs as the current user. - * Don't call directly; use utils_mkdir_recursive(). - * - * This function is ominously marked as "unsafe" since it should only - * be called by a caller that has transitioned to the uid and gid under which - * the directory creation should occur. - */ -LTTNG_HIDDEN -int _utils_mkdir_recursive_unsafe(const char *path, mode_t mode) -{ - char *p, tmp[PATH_MAX]; - size_t len; - int ret; - - assert(path); - - ret = snprintf(tmp, sizeof(tmp), "%s", path); - if (ret < 0) { - PERROR("snprintf mkdir"); - goto error; - } - - len = ret; - if (tmp[len - 1] == '/') { - tmp[len - 1] = 0; - } - - for (p = tmp + 1; *p; p++) { - if (*p == '/') { - *p = 0; - if (tmp[strlen(tmp) - 1] == '.' && - tmp[strlen(tmp) - 2] == '.' && - tmp[strlen(tmp) - 3] == '/') { - ERR("Using '/../' is not permitted in the trace path (%s)", - tmp); - ret = -1; - goto error; - } - ret = mkdir_check_exists(tmp, mode); - if (ret < 0) { - if (errno != EACCES) { - PERROR("mkdir recursive"); - ret = -errno; - goto error; - } - } - *p = '/'; - } - } - - ret = mkdir_check_exists(tmp, mode); - if (ret < 0) { - PERROR("mkdir recursive last element"); - ret = -errno; - } - -error: + struct lttng_directory_handle handle; + struct lttng_credentials creds = { + .uid = (uid_t) uid, + .gid = (gid_t) gid, + }; + + (void) lttng_directory_handle_init(&handle, NULL); + ret = lttng_directory_handle_create_subdirectory_as_user( + &handle, path, mode, + (uid >= 0 || gid >= 0) ? &creds : NULL); + lttng_directory_handle_fini(&handle); return ret; } @@ -803,18 +701,17 @@ LTTNG_HIDDEN int utils_mkdir_recursive(const char *path, mode_t mode, int uid, int gid) { int ret; - - if (uid < 0 || gid < 0) { - /* Run as current user. */ - ret = _utils_mkdir_recursive_unsafe(path, mode); - } else { - ret = run_as_mkdir_recursive(path, mode, uid, gid); - } - if (ret < 0) { - PERROR("mkdir %s, uid %d, gid %d", path ? path : "NULL", - uid, gid); - } - + struct lttng_directory_handle handle; + struct lttng_credentials creds = { + .uid = (uid_t) uid, + .gid = (gid_t) gid, + }; + + (void) lttng_directory_handle_init(&handle, NULL); + ret = lttng_directory_handle_create_subdirectory_recursive_as_user( + &handle, path, mode, + (uid >= 0 || gid >= 0) ? &creds : NULL); + lttng_directory_handle_fini(&handle); return ret; } diff --git a/src/common/utils.h b/src/common/utils.h index 9e3fa60cd..f372bf464 100644 --- a/src/common/utils.h +++ b/src/common/utils.h @@ -23,6 +23,8 @@ #include #include +#include + #define KIBI_LOG2 10 #define MEBI_LOG2 20 #define GIBI_LOG2 30