fs-util: teach xopenat_full() about XO_SOCKET and XO_TRIGGER_AUTOMOUNT
XO_SOCKET asks for the opened inode to be verified as a socket, analogous to the existing XO_REGULAR. A new fd_verify_socket() helper is added to stat-util to perform the check on an opened fd. XO_TRIGGER_AUTOMOUNT asks O_PATH opens to trigger automounts by going via open_tree() without OPEN_TREE_CLONE, falling back to openat() if the kernel or sandbox rejects open_tree().
This commit is contained in:
+80
-3
@@ -3,6 +3,7 @@
|
||||
#include <linux/falloc.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/mount.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
@@ -1130,6 +1131,45 @@ int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, b
|
||||
}
|
||||
}
|
||||
|
||||
static int openat_with_automount(int dir_fd, const char *path, int open_flags, mode_t mode) {
|
||||
/* When XO_TRIGGER_AUTOMOUNT is set we want to trigger automounts on the path. open() with O_PATH
|
||||
* does not do that, so we use open_tree() without OPEN_TREE_CLONE which is equivalent to open() with
|
||||
* O_PATH except that it does trigger automounts. Some sandboxes reject open_tree() with EPERM or
|
||||
* ENOSYS, in which case we fall back to plain openat(): autofs wouldn't work inside a restricted
|
||||
* mount namespace anyway. */
|
||||
|
||||
static bool can_open_tree = true;
|
||||
int r;
|
||||
|
||||
assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
|
||||
assert(path);
|
||||
|
||||
if (can_open_tree) {
|
||||
r = RET_NERRNO(open_tree(dir_fd, path,
|
||||
OPEN_TREE_CLOEXEC |
|
||||
(FLAGS_SET(open_flags, O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0)));
|
||||
if (r >= 0) {
|
||||
/* open_tree() doesn't honor O_DIRECTORY, so enforce it ourselves to match
|
||||
* the openat() fallback's behavior. */
|
||||
if (FLAGS_SET(open_flags, O_DIRECTORY)) {
|
||||
int q = fd_verify_directory(r);
|
||||
if (q < 0) {
|
||||
safe_close(r);
|
||||
return q;
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
if (r != -EPERM && !ERRNO_IS_NEG_NOT_SUPPORTED(r))
|
||||
return r;
|
||||
|
||||
can_open_tree = false;
|
||||
}
|
||||
|
||||
return RET_NERRNO(openat(dir_fd, path, open_flags, mode));
|
||||
}
|
||||
|
||||
int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode) {
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
bool made_dir = false, made_file = false;
|
||||
@@ -1137,8 +1177,19 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_
|
||||
|
||||
assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT));
|
||||
|
||||
/* An inode cannot be both a directory and a regular file at the same time. */
|
||||
/* An inode can only be one of a directory, a regular file or a socket at the same time. */
|
||||
assert(!(FLAGS_SET(open_flags, O_DIRECTORY) && FLAGS_SET(xopen_flags, XO_REGULAR)));
|
||||
assert(!(FLAGS_SET(xopen_flags, XO_REGULAR) && FLAGS_SET(xopen_flags, XO_SOCKET)));
|
||||
assert(!(FLAGS_SET(open_flags, O_DIRECTORY) && FLAGS_SET(xopen_flags, XO_SOCKET)));
|
||||
/* Sockets cannot be open()ed, only pinned via O_PATH. */
|
||||
assert(!FLAGS_SET(xopen_flags, XO_SOCKET) || FLAGS_SET(open_flags, O_PATH));
|
||||
/* XO_TRIGGER_AUTOMOUNT requires O_PATH and does not support creating inodes. XO_SUBVOLUME
|
||||
* requires O_CREAT, and XO_NOCOW needs a writable fd for its chattr ioctl, so neither is
|
||||
* compatible with XO_TRIGGER_AUTOMOUNT. */
|
||||
assert(!FLAGS_SET(xopen_flags, XO_TRIGGER_AUTOMOUNT) ||
|
||||
(FLAGS_SET(open_flags, O_PATH) && !FLAGS_SET(open_flags, O_CREAT)));
|
||||
assert(!(FLAGS_SET(xopen_flags, XO_TRIGGER_AUTOMOUNT) && FLAGS_SET(xopen_flags, XO_SUBVOLUME)));
|
||||
assert(!(FLAGS_SET(xopen_flags, XO_TRIGGER_AUTOMOUNT) && FLAGS_SET(xopen_flags, XO_NOCOW)));
|
||||
|
||||
/* This is like openat(), but has a few tricks up its sleeves, extending behaviour:
|
||||
*
|
||||
@@ -1153,6 +1204,10 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_
|
||||
*
|
||||
* • if XO_REGULAR is specified will return an error if inode is not a regular file.
|
||||
*
|
||||
* • if XO_SOCKET is specified will return an error if inode is not a socket.
|
||||
*
|
||||
* • if XO_TRIGGER_AUTOMOUNT is specified O_PATH fds will trigger automounts.
|
||||
*
|
||||
* • If mode is specified as MODE_INVALID, we'll use 0755 for dirs, and 0644 for regular files.
|
||||
*
|
||||
* • The dir fd can be passed as XAT_FDROOT, in which case any relative paths will be taken relative to the root fs.
|
||||
@@ -1170,6 +1225,12 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_
|
||||
return r;
|
||||
}
|
||||
|
||||
if (FLAGS_SET(xopen_flags, XO_SOCKET)) {
|
||||
r = fd_verify_socket(dir_fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return fd_reopen(dir_fd, open_flags & ~O_NOFOLLOW);
|
||||
}
|
||||
|
||||
@@ -1217,9 +1278,11 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_
|
||||
* first */
|
||||
|
||||
if (FLAGS_SET(open_flags, O_PATH)) {
|
||||
fd = openat(dir_fd, path, open_flags, mode);
|
||||
fd = FLAGS_SET(xopen_flags, XO_TRIGGER_AUTOMOUNT) ?
|
||||
openat_with_automount(dir_fd, path, open_flags, mode) :
|
||||
RET_NERRNO(openat(dir_fd, path, open_flags, mode));
|
||||
if (fd < 0) {
|
||||
r = -errno;
|
||||
r = fd;
|
||||
goto error;
|
||||
}
|
||||
|
||||
@@ -1266,7 +1329,15 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (FLAGS_SET(xopen_flags, XO_TRIGGER_AUTOMOUNT)) {
|
||||
fd = openat_with_automount(dir_fd, path, open_flags, mode);
|
||||
if (fd < 0) {
|
||||
r = fd;
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
/* XO_SOCKET also lands here: it requires O_PATH (see asserts above) so openat() pins
|
||||
* the inode without connecting, and fd_verify_socket() below enforces the type. */
|
||||
fd = openat_report_new(dir_fd, path, open_flags, mode, &made_file);
|
||||
if (fd < 0) {
|
||||
r = fd;
|
||||
@@ -1274,6 +1345,12 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_
|
||||
}
|
||||
}
|
||||
|
||||
if (FLAGS_SET(xopen_flags, XO_SOCKET)) {
|
||||
r = fd_verify_socket(fd);
|
||||
if (r < 0)
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (call_label_ops_post) {
|
||||
call_label_ops_post = false;
|
||||
|
||||
|
||||
+6
-4
@@ -109,10 +109,12 @@ int posix_fallocate_loop(int fd, uint64_t offset, uint64_t size);
|
||||
int parse_cifs_service(const char *s, char **ret_host, char **ret_service, char **ret_path);
|
||||
|
||||
typedef enum XOpenFlags {
|
||||
XO_LABEL = 1 << 0, /* When creating: relabel */
|
||||
XO_SUBVOLUME = 1 << 1, /* When creating as directory: make it a subvolume */
|
||||
XO_NOCOW = 1 << 2, /* Enable NOCOW mode after opening */
|
||||
XO_REGULAR = 1 << 3, /* Fail if the inode is not a regular file */
|
||||
XO_LABEL = 1 << 0, /* When creating: relabel */
|
||||
XO_SUBVOLUME = 1 << 1, /* When creating as directory: make it a subvolume */
|
||||
XO_NOCOW = 1 << 2, /* Enable NOCOW mode after opening */
|
||||
XO_REGULAR = 1 << 3, /* Fail if the inode is not a regular file */
|
||||
XO_SOCKET = 1 << 4, /* Fail if the inode is not a socket */
|
||||
XO_TRIGGER_AUTOMOUNT = 1 << 5, /* Trigger automounts via open_tree(). Requires O_PATH. */
|
||||
} XOpenFlags;
|
||||
|
||||
int open_mkdir_at_full(int dirfd, const char *path, int flags, XOpenFlags xopen_flags, mode_t mode);
|
||||
|
||||
@@ -181,6 +181,13 @@ int statx_verify_socket(const struct statx *stx) {
|
||||
return mode_verify_socket(stx->stx_mode);
|
||||
}
|
||||
|
||||
int fd_verify_socket(int fd) {
|
||||
if (IN_SET(fd, AT_FDCWD, XAT_FDROOT))
|
||||
return -EISDIR;
|
||||
|
||||
return verify_stat_at(fd, /* path= */ NULL, /* follow= */ false, stat_verify_socket, /* verify= */ true);
|
||||
}
|
||||
|
||||
int is_socket(const char *path) {
|
||||
assert(!isempty(path));
|
||||
return verify_stat_at(AT_FDCWD, path, /* follow= */ true, stat_verify_socket, /* verify= */ false);
|
||||
|
||||
@@ -23,6 +23,7 @@ int is_symlink(const char *path);
|
||||
|
||||
int stat_verify_socket(const struct stat *st);
|
||||
int statx_verify_socket(const struct statx *stx);
|
||||
int fd_verify_socket(int fd);
|
||||
int is_socket(const char *path);
|
||||
|
||||
int stat_verify_linked(const struct stat *st);
|
||||
|
||||
Reference in New Issue
Block a user