#include "alloc-util.h"
#include "dirent-util.h"
#include "fd-util.h"
-#include "fileio.h"
#include "fs-util.h"
+ #include "locale-util.h"
#include "log.h"
#include "macro.h"
#include "missing.h"
#include "string-util.h"
#include "strv.h"
#include "time-util.h"
+#include "tmpfile-util.h"
#include "user-util.h"
#include "util.h"
}
int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) {
+ char fd_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1];
+ _cleanup_close_ int fd = -1;
assert(path);
- /* Under the assumption that we are running privileged we
- * first change the access mode and only then hand out
+ /* Under the assumption that we are running privileged we first change the access mode and only then hand out
* ownership to avoid a window where access is too open. */
- if (mode != MODE_INVALID)
- if (chmod(path, mode) < 0)
+ fd = open(path, O_PATH|O_CLOEXEC|O_NOFOLLOW); /* Let's acquire an O_PATH fd, as precaution to change mode/owner
+ * on the same file */
+ if (fd < 0)
+ return -errno;
+
+ xsprintf(fd_path, "/proc/self/fd/%i", fd);
+
+ if (mode != MODE_INVALID) {
+
+ if ((mode & S_IFMT) != 0) {
+ struct stat st;
+
+ if (stat(fd_path, &st) < 0)
+ return -errno;
+
+ if ((mode & S_IFMT) != (st.st_mode & S_IFMT))
+ return -EINVAL;
+ }
+
+ if (chmod(fd_path, mode & 07777) < 0)
return -errno;
+ }
if (uid != UID_INVALID || gid != GID_INVALID)
- if (chown(path, uid, gid) < 0)
+ if (chown(fd_path, uid, gid) < 0)
return -errno;
return 0;
}
int fchmod_and_chown(int fd, mode_t mode, uid_t uid, gid_t gid) {
- /* Under the assumption that we are running privileged we
- * first change the access mode and only then hand out
+ /* Under the assumption that we are running privileged we first change the access mode and only then hand out
* ownership to avoid a window where access is too open. */
- if (mode != MODE_INVALID)
- if (fchmod(fd, mode) < 0)
+ if (mode != MODE_INVALID) {
+
+ if ((mode & S_IFMT) != 0) {
+ struct stat st;
+
+ if (fstat(fd, &st) < 0)
+ return -errno;
+
+ if ((mode & S_IFMT) != (st.st_mode & S_IFMT))
+ return -EINVAL;
+ }
+
+ if (fchmod(fd, mode & 0777) < 0)
return -errno;
+ }
if (uid != UID_INVALID || gid != GID_INVALID)
if (fchown(fd, uid, gid) < 0)
* fchownat() does. */
xsprintf(procfs_path, "/proc/self/fd/%i", fd);
-
if (chmod(procfs_path, m) < 0)
return -errno;
return r;
}
- static bool safe_transition(const struct stat *a, const struct stat *b) {
+ static bool unsafe_transition(const struct stat *a, const struct stat *b) {
/* Returns true if the transition from a to b is safe, i.e. that we never transition from unprivileged to
* privileged files or directories. Why bother? So that unprivileged code can't symlink to privileged files
* making us believe we read something safe even though it isn't safe in the specific context we open it in. */
if (a->st_uid == 0) /* Transitioning from privileged to unprivileged is always fine */
- return true;
+ return false;
- return a->st_uid == b->st_uid; /* Otherwise we need to stay within the same UID */
+ return a->st_uid != b->st_uid; /* Otherwise we need to stay within the same UID */
+ }
+
+ static int log_unsafe_transition(int a, int b, const char *path, unsigned flags) {
+ _cleanup_free_ char *n1 = NULL, *n2 = NULL;
+
+ if (!FLAGS_SET(flags, CHASE_WARN))
+ return -ENOLINK;
+
+ (void) fd_get_path(a, &n1);
+ (void) fd_get_path(b, &n2);
+
+ return log_warning_errno(SYNTHETIC_ERRNO(ENOLINK),
+ "Detected unsafe path transition %s %s %s during canonicalization of %s.",
+ n1, special_glyph(ARROW), n2, path);
+ }
+
+ static int log_autofs_mount_point(int fd, const char *path, unsigned flags) {
+ _cleanup_free_ char *n1 = NULL;
+
+ if (!FLAGS_SET(flags, CHASE_WARN))
+ return -EREMOTE;
+
+ (void) fd_get_path(fd, &n1);
+
+ return log_warning_errno(SYNTHETIC_ERRNO(EREMOTE),
+ "Detected autofs mount point %s during canonicalization of %s.",
+ n1, path);
}
int chase_symlinks(const char *path, const char *original_root, unsigned flags, char **ret) {
* path is fully normalized, and == 0 for each normalization step. This may be combined with
* CHASE_NONEXISTENT, in which case 1 is returned when a component is not found.
*
+ * 4. With CHASE_SAFE: in this case the path must not contain unsafe transitions, i.e. transitions from
+ * unprivileged to privileged files or directories. In such cases the return value is -ENOLINK. If
+ * CHASE_WARN is also set a warning describing the unsafe transition is emitted.
+ *
+ * 5. With CHASE_NO_AUTOFS: in this case if an autofs mount point is encountered, the path normalization is
+ * aborted and -EREMOTE is returned. If CHASE_WARN is also set a warning showing the path of the mount point
+ * is emitted.
+ *
* */
/* A root directory of "/" or "" is identical to none */
if (fstat(fd_parent, &st) < 0)
return -errno;
- if (!safe_transition(&previous_stat, &st))
- return -EPERM;
+ if (unsafe_transition(&previous_stat, &st))
+ return log_unsafe_transition(fd, fd_parent, path, flags);
previous_stat = st;
}
if (fstat(child, &st) < 0)
return -errno;
if ((flags & CHASE_SAFE) &&
- !safe_transition(&previous_stat, &st))
- return -EPERM;
+ unsafe_transition(&previous_stat, &st))
+ return log_unsafe_transition(fd, child, path, flags);
previous_stat = st;
if ((flags & CHASE_NO_AUTOFS) &&
fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0)
- return -EREMOTE;
+ return log_autofs_mount_point(child, path, flags);
if (S_ISLNK(st.st_mode) && !((flags & CHASE_NOFOLLOW) && isempty(todo))) {
char *joined;
if (fstat(fd, &st) < 0)
return -errno;
- if (!safe_transition(&previous_stat, &st))
- return -EPERM;
+ if (unsafe_transition(&previous_stat, &st))
+ return log_unsafe_transition(child, fd, path, flags);
previous_stat = st;
}
#include "label.h"
#include "log.h"
#include "macro.h"
+#include "main-func.h"
#include "missing.h"
#include "mkdir.h"
-#include "mount-util.h"
+#include "mountpoint-util.h"
#include "pager.h"
#include "parse-util.h"
#include "path-lookup.h"
static OrderedHashmap *items = NULL, *globs = NULL;
static Set *unix_sockets = NULL;
+STATIC_DESTRUCTOR_REGISTER(items, ordered_hashmap_freep);
+STATIC_DESTRUCTOR_REGISTER(globs, ordered_hashmap_freep);
+STATIC_DESTRUCTOR_REGISTER(unix_sockets, set_free_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_include_prefixes, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_exclude_prefixes, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
+
static int specifier_machine_id_safe(char specifier, void *data, void *userdata, char **ret);
static int specifier_directory(char specifier, void *data, void *userdata, char **ret);
return NULL;
}
-static void load_unix_sockets(void) {
+static int load_unix_sockets(void) {
+ _cleanup_set_free_free_ Set *sockets = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
if (unix_sockets)
- return;
+ return 0;
/* We maintain a cache of the sockets we found in /proc/net/unix to speed things up a little. */
- unix_sockets = set_new(&path_hash_ops);
- if (!unix_sockets) {
- log_oom();
- return;
- }
+ sockets = set_new(&path_hash_ops);
+ if (!sockets)
+ return log_oom();
f = fopen("/proc/net/unix", "re");
- if (!f) {
- log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno,
- "Failed to open /proc/net/unix, ignoring: %m");
- goto fail;
- }
+ if (!f)
+ return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno,
+ "Failed to open /proc/net/unix, ignoring: %m");
/* Skip header */
r = read_line(f, LONG_LINE_MAX, NULL);
- if (r < 0) {
- log_warning_errno(r, "Failed to skip /proc/net/unix header line: %m");
- goto fail;
- }
- if (r == 0) {
- log_warning("Premature end of file reading /proc/net/unix.");
- goto fail;
- }
+ if (r < 0)
+ return log_warning_errno(r, "Failed to skip /proc/net/unix header line: %m");
+ if (r == 0)
+ return log_warning_errno(SYNTHETIC_ERRNO(EIO), "Premature end of file reading /proc/net/unix.");
for (;;) {
- _cleanup_free_ char *line = NULL;
- char *p, *s;
+ _cleanup_free_ char *line = NULL, *s = NULL;
+ char *p;
r = read_line(f, LONG_LINE_MAX, &line);
- if (r < 0) {
- log_warning_errno(r, "Failed to read /proc/net/unix line, ignoring: %m");
- goto fail;
- }
+ if (r < 0)
+ return log_warning_errno(r, "Failed to read /proc/net/unix line, ignoring: %m");
if (r == 0) /* EOF */
break;
continue;
s = strdup(p);
- if (!s) {
- log_oom();
- goto fail;
- }
+ if (!s)
+ return log_oom();
path_simplify(s, false);
- r = set_consume(unix_sockets, s);
- if (r < 0 && r != -EEXIST) {
- log_warning_errno(r, "Failed to add AF_UNIX socket to set, ignoring: %m");
- goto fail;
- }
- }
+ r = set_consume(sockets, s);
+ if (r == -EEXIST)
+ continue;
+ if (r < 0)
+ return log_warning_errno(r, "Failed to add AF_UNIX socket to set, ignoring: %m");
- return;
+ TAKE_PTR(s);
+ }
-fail:
- unix_sockets = set_free_free(unix_sockets);
+ unix_sockets = TAKE_PTR(sockets);
+ return 1;
}
static bool unix_socket_alive(const char *fn) {
assert(fn);
- load_unix_sockets();
-
- if (unix_sockets)
- return !!set_get(unix_sockets, (char*) fn);
+ if (load_unix_sockets() < 0)
+ return true; /* We don't know, so assume yes */
- /* We don't know, so assume yes */
- return true;
+ return !!set_get(unix_sockets, (char*) fn);
}
static int dir_is_mount_point(DIR *d, const char *subdir) {
if (!dn)
return log_oom();
- fd = chase_symlinks(dn, NULL, CHASE_OPEN|CHASE_SAFE, NULL);
- if (fd == -EPERM)
- return log_error_errno(fd, "Unsafe symlinks encountered in %s, refusing.", path);
- if (fd < 0)
+ fd = chase_symlinks(dn, NULL, CHASE_OPEN|CHASE_SAFE|CHASE_WARN, NULL);
+ if (fd < 0 && fd != -ENOLINK)
return log_error_errno(fd, "Failed to validate path %s: %m", path);
return fd;
"Failed to open invalid path '%s'.",
path);
- fd = chase_symlinks(path, NULL, CHASE_OPEN|CHASE_SAFE|CHASE_NOFOLLOW, NULL);
- if (fd == -EPERM)
- return log_error_errno(fd, "Unsafe symlinks encountered in %s, refusing.", path);
- if (fd < 0)
+ fd = chase_symlinks(path, NULL, CHASE_OPEN|CHASE_SAFE|CHASE_WARN|CHASE_NOFOLLOW, NULL);
+ if (fd < 0 && fd != -ENOLINK)
return log_error_errno(fd, "Failed to validate path %s: %m", path);
return fd;
else {
_cleanup_free_ char *de_path = NULL;
- de_path = path_join(NULL, path, de->d_name);
+ de_path = path_join(path, de->d_name);
if (!de_path)
q = log_oom();
else
i->done |= operation;
- r = chase_symlinks(i->path, NULL, CHASE_NO_AUTOFS, NULL);
+ r = chase_symlinks(i->path, NULL, CHASE_NO_AUTOFS|CHASE_WARN, NULL);
if (r == -EREMOTE) {
- log_debug_errno(r, "Item '%s' is below autofs, skipping.", i->path);
+ log_notice_errno(r, "Skipping %s", i->path);
return 0;
- } else if (r < 0)
+ }
+ if (r < 0)
log_debug_errno(r, "Failed to determine whether '%s' is below autofs, ignoring: %m", i->path);
r = FLAGS_SET(operation, OPERATION_CREATE) ? create_item(i) : 0;
#endif
}
-static void item_array_free(ItemArray *a) {
+static ItemArray* item_array_free(ItemArray *a) {
size_t n;
if (!a)
- return;
+ return NULL;
for (n = 0; n < a->n_items; n++)
item_free_contents(a->items + n);
set_free(a->children);
free(a->items);
- free(a);
+ return mfree(a);
}
static int item_compare(const Item *a, const Item *b) {
break;
case CREATE_CHAR_DEVICE:
- case CREATE_BLOCK_DEVICE: {
- unsigned major, minor;
-
+ case CREATE_BLOCK_DEVICE:
if (!i.argument) {
*invalid_config = true;
log_error("[%s:%u] Device file requires argument.", fname, line);
return -EBADMSG;
}
- if (sscanf(i.argument, "%u:%u", &major, &minor) != 2) {
+ r = parse_dev(i.argument, &i.major_minor);
+ if (r < 0) {
*invalid_config = true;
- log_error("[%s:%u] Can't parse device file major/minor '%s'.", fname, line, i.argument);
+ log_error_errno(r, "[%s:%u] Can't parse device file major/minor '%s'.", fname, line, i.argument);
return -EBADMSG;
}
- i.major_minor = makedev(major, minor);
break;
- }
case SET_XATTR:
case RECURSIVE_SET_XATTR:
return 0;
}
-int main(int argc, char *argv[]) {
+DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(item_array_hash_ops, char, string_hash_func, string_compare_func,
+ ItemArray, item_array_free);
+
+static int run(int argc, char *argv[]) {
_cleanup_strv_free_ char **config_dirs = NULL;
- int r, k, r_process = 0;
bool invalid_config = false;
Iterator iterator;
ItemArray *a;
PHASE_CREATE,
_PHASE_MAX
} phase;
+ int r, k;
r = parse_argv(argc, argv);
if (r <= 0)
- goto finish;
+ return r;
log_setup_service();
if (arg_user) {
r = user_config_paths(&config_dirs);
- if (r < 0) {
- log_error_errno(r, "Failed to initialize configuration directory list: %m");
- goto finish;
- }
+ if (r < 0)
+ return log_error_errno(r, "Failed to initialize configuration directory list: %m");
} else {
config_dirs = strv_split_nulstr(CONF_PATHS_NULSTR("tmpfiles.d"));
- if (!config_dirs) {
- r = log_oom();
- goto finish;
- }
+ if (!config_dirs)
+ return log_oom();
}
if (DEBUG_LOGGING) {
if (arg_cat_config) {
(void) pager_open(arg_pager_flags);
- r = cat_config(config_dirs, argv + optind);
- goto finish;
+ return cat_config(config_dirs, argv + optind);
}
umask(0022);
mac_selinux_init();
- items = ordered_hashmap_new(&string_hash_ops);
- globs = ordered_hashmap_new(&string_hash_ops);
-
- if (!items || !globs) {
- r = log_oom();
- goto finish;
- }
+ items = ordered_hashmap_new(&item_array_hash_ops);
+ globs = ordered_hashmap_new(&item_array_hash_ops);
+ if (!items || !globs)
+ return log_oom();
/* If command line arguments are specified along with --replace, read all
* configuration files and insert the positional arguments at the specified
else
r = parse_arguments(config_dirs, argv + optind, &invalid_config);
if (r < 0)
- goto finish;
+ return r;
/* Let's now link up all child/parent relationships */
ORDERED_HASHMAP_FOREACH(a, items, iterator) {
r = link_parent(a);
if (r < 0)
- goto finish;
+ return r;
}
ORDERED_HASHMAP_FOREACH(a, globs, iterator) {
r = link_parent(a);
if (r < 0)
- goto finish;
+ return r;
}
/* If multiple operations are requested, let's first run the remove/clean operations, and only then the create
/* The non-globbing ones usually create things, hence we apply them first */
ORDERED_HASHMAP_FOREACH(a, items, iterator) {
k = process_item_array(a, op);
- if (k < 0 && r_process == 0)
- r_process = k;
+ if (k < 0 && r >= 0)
+ r = k;
}
/* The globbing ones usually alter things, hence we apply them second. */
ORDERED_HASHMAP_FOREACH(a, globs, iterator) {
k = process_item_array(a, op);
- if (k < 0 && r_process == 0)
- r_process = k;
+ if (k < 0 && r >= 0)
+ r = k;
}
}
-finish:
- pager_close();
-
- ordered_hashmap_free_with_destructor(items, item_array_free);
- ordered_hashmap_free_with_destructor(globs, item_array_free);
-
- free(arg_include_prefixes);
- free(arg_exclude_prefixes);
- free(arg_root);
-
- set_free_free(unix_sockets);
-
- mac_selinux_finish();
-
- if (r < 0 || ERRNO_IS_RESOURCE(-r_process))
- return EXIT_FAILURE;
- else if (invalid_config)
+ if (ERRNO_IS_RESOURCE(-r))
+ return r;
+ if (invalid_config)
return EX_DATAERR;
- else if (r_process < 0)
+ if (r < 0)
return EX_CANTCREAT;
- else
- return EXIT_SUCCESS;
+ return 0;
}
+
+DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);