static void test_rearrange_stdio(void) { pid_t pid; int r; r = safe_fork("rearrange", FORK_WAIT|FORK_LOG, &pid); assert_se(r >= 0); if (r == 0) { _cleanup_free_ char *path = NULL; char buffer[10]; /* Child */ safe_close(STDERR_FILENO); /* Let's close an fd < 2, to make it more interesting */ assert_se(rearrange_stdio(-1, -1, -1) >= 0); assert_se(fd_get_path(STDIN_FILENO, &path) >= 0); assert_se(path_equal(path, "/dev/null")); path = mfree(path); assert_se(fd_get_path(STDOUT_FILENO, &path) >= 0); assert_se(path_equal(path, "/dev/null")); path = mfree(path); assert_se(fd_get_path(STDOUT_FILENO, &path) >= 0); assert_se(path_equal(path, "/dev/null")); path = mfree(path); safe_close(STDIN_FILENO); safe_close(STDOUT_FILENO); safe_close(STDERR_FILENO); { int pair[2]; assert_se(pipe(pair) >= 0); assert_se(pair[0] == 0); assert_se(pair[1] == 1); assert_se(fd_move_above_stdio(0) == 3); } assert_se(open("/dev/full", O_WRONLY|O_CLOEXEC) == 0); assert_se(acquire_data_fd("foobar", 6, 0) == 2); assert_se(rearrange_stdio(2, 0, 1) >= 0); assert_se(write(1, "x", 1) < 0 && errno == ENOSPC); assert_se(write(2, "z", 1) == 1); assert_se(read(3, buffer, sizeof(buffer)) == 1); assert_se(buffer[0] == 'z'); assert_se(read(0, buffer, sizeof(buffer)) == 6); assert_se(memcmp(buffer, "foobar", 6) == 0); assert_se(rearrange_stdio(-1, 1, 2) >= 0); assert_se(write(1, "a", 1) < 0 && errno == ENOSPC); assert_se(write(2, "y", 1) == 1); assert_se(read(3, buffer, sizeof(buffer)) == 1); assert_se(buffer[0] == 'y'); assert_se(fd_get_path(0, &path) >= 0); assert_se(path_equal(path, "/dev/null")); path = mfree(path); _exit(EXIT_SUCCESS); } }
int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) { _cleanup_closedir_ DIR *d = NULL; struct dirent *de; int ret = 0, r; struct statfs sfs; assert(fd >= 0); /* This returns the first error we run into, but nevertheless * tries to go on. This closes the passed fd. */ if (!(flags & REMOVE_PHYSICAL)) { r = fstatfs(fd, &sfs); if (r < 0) { safe_close(fd); return -errno; } if (is_physical_fs(&sfs)) { /* We refuse to clean physical file systems with this call, * unless explicitly requested. This is extra paranoia just * to be sure we never ever remove non-state data. */ _cleanup_free_ char *path = NULL; (void) fd_get_path(fd, &path); log_error("Attempted to remove disk file system under \"%s\", and we can't allow that.", strna(path)); safe_close(fd); return -EPERM; } } d = fdopendir(fd); if (!d) { safe_close(fd); return errno == ENOENT ? 0 : -errno; } FOREACH_DIRENT_ALL(de, d, return -errno) { bool is_dir; struct stat st; if (dot_or_dot_dot(de->d_name)) continue; if (de->d_type == DT_UNKNOWN || (de->d_type == DT_DIR && (root_dev || (flags & REMOVE_SUBVOLUME)))) { if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { if (ret == 0 && errno != ENOENT) ret = -errno; continue; } is_dir = S_ISDIR(st.st_mode); } else is_dir = de->d_type == DT_DIR; if (is_dir) { int subdir_fd; /* if root_dev is set, remove subdirectories only if device is same */ if (root_dev && st.st_dev != root_dev->st_dev) continue; subdir_fd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); if (subdir_fd < 0) { if (ret == 0 && errno != ENOENT) ret = -errno; continue; } /* Stop at mount points */ r = fd_is_mount_point(fd, de->d_name, 0); if (r < 0) { if (ret == 0 && r != -ENOENT) ret = r; safe_close(subdir_fd); continue; } if (r) { safe_close(subdir_fd); continue; } if ((flags & REMOVE_SUBVOLUME) && st.st_ino == 256) { /* This could be a subvolume, try to remove it */ r = btrfs_subvol_remove_fd(fd, de->d_name, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); if (r < 0) { if (!IN_SET(r, -ENOTTY, -EINVAL)) { if (ret == 0) ret = r; safe_close(subdir_fd); continue; } /* ENOTTY, then it wasn't a * btrfs subvolume, continue * below. */ } else { /* It was a subvolume, continue. */ safe_close(subdir_fd); continue; } } /* We pass REMOVE_PHYSICAL here, to avoid * doing the fstatfs() to check the file * system type again for each directory */ r = rm_rf_children(subdir_fd, flags | REMOVE_PHYSICAL, root_dev); if (r < 0 && ret == 0) ret = r; if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) { if (ret == 0 && errno != ENOENT) ret = -errno; } } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) { if (unlinkat(fd, de->d_name, 0) < 0) { if (ret == 0 && errno != ENOENT) ret = -errno; } } } return ret; }
void server_process_native_file( Server *s, int fd, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len) { struct stat st; bool sealed; int r; /* Data is in the passed fd, since it didn't fit in a * datagram. */ assert(s); assert(fd >= 0); /* If it's a memfd, check if it is sealed. If so, we can just * use map it and use it, and do not need to copy the data * out. */ sealed = memfd_get_sealed(fd) > 0; if (!sealed && (!ucred || ucred->uid != 0)) { _cleanup_free_ char *k = NULL; const char *e; /* If this is not a sealed memfd, and the peer is unknown or * unprivileged, then verify the path. */ r = fd_get_path(fd, &k); if (r < 0) { log_error_errno(r, "readlink(/proc/self/fd/%i) failed: %m", fd); return; } e = PATH_STARTSWITH_SET(k, "/dev/shm/", "/tmp/", "/var/tmp/"); if (!e) { log_error("Received file outside of allowed directories. Refusing."); return; } if (!filename_is_valid(e)) { log_error("Received file in subdirectory of allowed directories. Refusing."); return; } } if (fstat(fd, &st) < 0) { log_error_errno(errno, "Failed to stat passed file, ignoring: %m"); return; } if (!S_ISREG(st.st_mode)) { log_error("File passed is not regular. Ignoring."); return; } if (st.st_size <= 0) return; if (st.st_size > ENTRY_SIZE_MAX) { log_error("File passed too large. Ignoring."); return; } if (sealed) { void *p; size_t ps; /* The file is sealed, we can just map it and use it. */ ps = PAGE_ALIGN(st.st_size); p = mmap(NULL, ps, PROT_READ, MAP_PRIVATE, fd, 0); if (p == MAP_FAILED) { log_error_errno(errno, "Failed to map memfd, ignoring: %m"); return; } server_process_native_message(s, p, st.st_size, ucred, tv, label, label_len); assert_se(munmap(p, ps) >= 0); } else { _cleanup_free_ void *p = NULL; struct statvfs vfs; ssize_t n; if (fstatvfs(fd, &vfs) < 0) { log_error_errno(errno, "Failed to stat file system of passed file, ignoring: %m"); return; } /* Refuse operating on file systems that have * mandatory locking enabled, see: * * https://github.com/systemd/systemd/issues/1822 */ if (vfs.f_flag & ST_MANDLOCK) { log_error("Received file descriptor from file system with mandatory locking enabled, refusing."); return; } /* Make the fd non-blocking. On regular files this has * the effect of bypassing mandatory locking. Of * course, this should normally not be necessary given * the check above, but let's better be safe than * sorry, after all NFS is pretty confusing regarding * file system flags, and we better don't trust it, * and so is SMB. */ r = fd_nonblock(fd, true); if (r < 0) { log_error_errno(r, "Failed to make fd non-blocking, ignoring: %m"); return; } /* The file is not sealed, we can't map the file here, since * clients might then truncate it and trigger a SIGBUS for * us. So let's stupidly read it */ p = malloc(st.st_size); if (!p) { log_oom(); return; } n = pread(fd, p, st.st_size, 0); if (n < 0) log_error_errno(errno, "Failed to read file, ignoring: %m"); else if (n > 0) server_process_native_message(s, p, n, ucred, tv, label, label_len); } }