mirror of
https://github.com/systemd/systemd.git
synced 2024-11-23 18:23:32 +08:00
Merge pull request #10996 from poettering/oci-prep
Preparation for the nspawn-OCI work
This commit is contained in:
commit
b2ac2b01c8
23
TODO
23
TODO
@ -95,6 +95,29 @@ Features:
|
||||
|
||||
* bootspec.c: also enumerate EFI unified kernel images.
|
||||
|
||||
* maybe set a special xattr on cgroups that have delegate=yes set, to make it
|
||||
easy to mark cut points
|
||||
|
||||
* introduce an option (or replacement) for "systemctl show" that outputs all
|
||||
properties as JSON, similar to busctl's new JSON output. In contrast to that
|
||||
it should skip the variant type string though.
|
||||
|
||||
* augment CODE_FILE=, CODE_LINE= with something like CODE_BASE= or so which
|
||||
contains some identifier for the project, which allows us to include
|
||||
clickable links to source files generating these log messages. The identifier
|
||||
could be some abberviated URL prefix or so (taking inspiration from Go
|
||||
imports). For example, for systemd we could use
|
||||
CODE_BASE=github.com/systemd/systemd/blob/98b0b1123cc or so which is
|
||||
sufficient to build a link by prefixing "http://" and suffixing the
|
||||
CODE_FILE.
|
||||
|
||||
* when outputting log data with journalctl and the log data includes references
|
||||
to configuration files (CONFIG_FILE=), create a clickable link for it.
|
||||
|
||||
* Augment MESSAGE_ID with MESSAGE_BASE, in a similar fashion so that we can
|
||||
make clickable links from log messages carrying a MESSAGE_ID, that lead to
|
||||
some explanatory text online.
|
||||
|
||||
* maybe extend .path units to expose fanotify() per-mount change events
|
||||
|
||||
* Add a "systemctl list-units --by-slice" mode or so, which rearranges the
|
||||
|
@ -10,12 +10,13 @@
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "missing.h"
|
||||
#include "parse-util.h"
|
||||
#include "stat-util.h"
|
||||
|
||||
int block_get_whole_disk(dev_t d, dev_t *ret) {
|
||||
char p[SYS_BLOCK_PATH_MAX("/partition")];
|
||||
_cleanup_free_ char *s = NULL;
|
||||
unsigned n, m;
|
||||
dev_t devt;
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
@ -38,16 +39,16 @@ int block_get_whole_disk(dev_t d, dev_t *ret) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sscanf(s, "%u:%u", &m, &n);
|
||||
if (r != 2)
|
||||
return -EINVAL;
|
||||
r = parse_dev(s, &devt);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Only return this if it is really good enough for us. */
|
||||
xsprintf_sys_block_path(p, "/queue", makedev(m, n));
|
||||
xsprintf_sys_block_path(p, "/queue", devt);
|
||||
if (access(p, F_OK) < 0)
|
||||
return -ENOENT;
|
||||
|
||||
*ret = makedev(m, n);
|
||||
*ret = devt;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -85,8 +86,8 @@ int block_get_originating(dev_t dt, dev_t *ret) {
|
||||
_cleanup_free_ char *t = NULL;
|
||||
char p[SYS_BLOCK_PATH_MAX("/slaves")];
|
||||
struct dirent *de, *found = NULL;
|
||||
unsigned maj, min;
|
||||
const char *q;
|
||||
dev_t devt;
|
||||
int r;
|
||||
|
||||
/* For the specified block device tries to chase it through the layers, in case LUKS-style DM stacking is used,
|
||||
@ -148,13 +149,14 @@ int block_get_originating(dev_t dt, dev_t *ret) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (sscanf(t, "%u:%u", &maj, &min) != 2)
|
||||
r = parse_dev(t, &devt);
|
||||
if (r < 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (maj == 0)
|
||||
if (major(devt) == 0)
|
||||
return -ENOENT;
|
||||
|
||||
*ret = makedev(maj, min);
|
||||
*ret = devt;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -359,3 +359,128 @@ bool ambient_capabilities_supported(void) {
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
int capability_quintet_enforce(const CapabilityQuintet *q) {
|
||||
_cleanup_cap_free_ cap_t c = NULL;
|
||||
int r;
|
||||
|
||||
if (q->ambient != (uint64_t) -1) {
|
||||
unsigned long i;
|
||||
bool changed = false;
|
||||
|
||||
c = cap_get_proc();
|
||||
if (!c)
|
||||
return -errno;
|
||||
|
||||
/* In order to raise the ambient caps set we first need to raise the matching inheritable + permitted
|
||||
* cap */
|
||||
for (i = 0; i <= cap_last_cap(); i++) {
|
||||
uint64_t m = UINT64_C(1) << i;
|
||||
cap_value_t cv = (cap_value_t) i;
|
||||
cap_flag_value_t old_value_inheritable, old_value_permitted;
|
||||
|
||||
if ((q->ambient & m) == 0)
|
||||
continue;
|
||||
|
||||
if (cap_get_flag(c, cv, CAP_INHERITABLE, &old_value_inheritable) < 0)
|
||||
return -errno;
|
||||
if (cap_get_flag(c, cv, CAP_PERMITTED, &old_value_permitted) < 0)
|
||||
return -errno;
|
||||
|
||||
if (old_value_inheritable == CAP_SET && old_value_permitted == CAP_SET)
|
||||
continue;
|
||||
|
||||
if (cap_set_flag(c, CAP_INHERITABLE, 1, &cv, CAP_SET) < 0)
|
||||
return -errno;
|
||||
|
||||
if (cap_set_flag(c, CAP_PERMITTED, 1, &cv, CAP_SET) < 0)
|
||||
return -errno;
|
||||
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
if (cap_set_proc(c) < 0)
|
||||
return -errno;
|
||||
|
||||
r = capability_ambient_set_apply(q->ambient, false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (q->inheritable != (uint64_t) -1 || q->permitted != (uint64_t) -1 || q->effective != (uint64_t) -1) {
|
||||
bool changed = false;
|
||||
unsigned long i;
|
||||
|
||||
if (!c) {
|
||||
c = cap_get_proc();
|
||||
if (!c)
|
||||
return -errno;
|
||||
}
|
||||
|
||||
for (i = 0; i <= cap_last_cap(); i++) {
|
||||
uint64_t m = UINT64_C(1) << i;
|
||||
cap_value_t cv = (cap_value_t) i;
|
||||
|
||||
if (q->inheritable != (uint64_t) -1) {
|
||||
cap_flag_value_t old_value, new_value;
|
||||
|
||||
if (cap_get_flag(c, cv, CAP_INHERITABLE, &old_value) < 0)
|
||||
return -errno;
|
||||
|
||||
new_value = (q->inheritable & m) ? CAP_SET : CAP_CLEAR;
|
||||
|
||||
if (old_value != new_value) {
|
||||
changed = true;
|
||||
|
||||
if (cap_set_flag(c, CAP_INHERITABLE, 1, &cv, new_value) < 0)
|
||||
return -errno;
|
||||
}
|
||||
}
|
||||
|
||||
if (q->permitted != (uint64_t) -1) {
|
||||
cap_flag_value_t old_value, new_value;
|
||||
|
||||
if (cap_get_flag(c, cv, CAP_PERMITTED, &old_value) < 0)
|
||||
return -errno;
|
||||
|
||||
new_value = (q->permitted & m) ? CAP_SET : CAP_CLEAR;
|
||||
|
||||
if (old_value != new_value) {
|
||||
changed = true;
|
||||
|
||||
if (cap_set_flag(c, CAP_PERMITTED, 1, &cv, new_value) < 0)
|
||||
return -errno;
|
||||
}
|
||||
}
|
||||
|
||||
if (q->effective != (uint64_t) -1) {
|
||||
cap_flag_value_t old_value, new_value;
|
||||
|
||||
if (cap_get_flag(c, cv, CAP_EFFECTIVE, &old_value) < 0)
|
||||
return -errno;
|
||||
|
||||
new_value = (q->effective & m) ? CAP_SET : CAP_CLEAR;
|
||||
|
||||
if (old_value != new_value) {
|
||||
changed = true;
|
||||
|
||||
if (cap_set_flag(c, CAP_EFFECTIVE, 1, &cv, new_value) < 0)
|
||||
return -errno;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (changed)
|
||||
if (cap_set_proc(c) < 0)
|
||||
return -errno;
|
||||
}
|
||||
|
||||
if (q->bounding != (uint64_t) -1) {
|
||||
r = capability_bounding_set_drop(q->bounding, false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -43,3 +43,27 @@ bool ambient_capabilities_supported(void);
|
||||
/* Identical to linux/capability.h's CAP_TO_MASK(), but uses an unsigned 1U instead of a signed 1 for shifting left, in
|
||||
* order to avoid complaints about shifting a signed int left by 31 bits, which would make it negative. */
|
||||
#define CAP_TO_MASK_CORRECTED(x) (1U << ((x) & 31U))
|
||||
|
||||
typedef struct CapabilityQuintet {
|
||||
/* Stores all five types of capabilities in one go. Note that we use (uint64_t) -1 for unset here. This hence
|
||||
* needs to be updated as soon as Linux learns more than 63 caps. */
|
||||
uint64_t effective;
|
||||
uint64_t bounding;
|
||||
uint64_t inheritable;
|
||||
uint64_t permitted;
|
||||
uint64_t ambient;
|
||||
} CapabilityQuintet;
|
||||
|
||||
assert_cc(CAP_LAST_CAP < 64);
|
||||
|
||||
#define CAPABILITY_QUINTET_NULL { (uint64_t) -1, (uint64_t) -1, (uint64_t) -1, (uint64_t) -1, (uint64_t) -1 }
|
||||
|
||||
static inline bool capability_quintet_is_set(const CapabilityQuintet *q) {
|
||||
return q->effective != (uint64_t) -1 ||
|
||||
q->bounding != (uint64_t) -1 ||
|
||||
q->inheritable != (uint64_t) -1 ||
|
||||
q->permitted != (uint64_t) -1 ||
|
||||
q->ambient != (uint64_t) -1;
|
||||
}
|
||||
|
||||
int capability_quintet_enforce(const CapabilityQuintet *q);
|
||||
|
@ -211,31 +211,62 @@ int readlink_and_make_absolute(const char *p, char **r) {
|
||||
}
|
||||
|
||||
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)
|
||||
@ -263,7 +294,6 @@ int fchmod_opath(int fd, mode_t m) {
|
||||
* fchownat() does. */
|
||||
|
||||
xsprintf(procfs_path, "/proc/self/fd/%i", fd);
|
||||
|
||||
if (chmod(procfs_path, m) < 0)
|
||||
return -errno;
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "missing.h"
|
||||
#include "parse-util.h"
|
||||
#include "process-util.h"
|
||||
#include "stat-util.h"
|
||||
#include "string-util.h"
|
||||
|
||||
int parse_boolean(const char *v) {
|
||||
@ -731,17 +732,30 @@ int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high) {
|
||||
}
|
||||
|
||||
int parse_dev(const char *s, dev_t *ret) {
|
||||
const char *major;
|
||||
unsigned x, y;
|
||||
dev_t d;
|
||||
size_t n;
|
||||
int r;
|
||||
|
||||
if (sscanf(s, "%u:%u", &x, &y) != 2)
|
||||
n = strspn(s, DIGITS);
|
||||
if (n == 0)
|
||||
return -EINVAL;
|
||||
if (s[n] != ':')
|
||||
return -EINVAL;
|
||||
|
||||
d = makedev(x, y);
|
||||
if ((unsigned) major(d) != x || (unsigned) minor(d) != y)
|
||||
return -EINVAL;
|
||||
major = strndupa(s, n);
|
||||
r = safe_atou(major, &x);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*ret = d;
|
||||
r = safe_atou(s + n + 1, &y);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!DEVICE_MAJOR_VALID(x) || !DEVICE_MINOR_VALID(y))
|
||||
return -ERANGE;
|
||||
|
||||
*ret = makedev(x, y);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -481,18 +481,68 @@ bool path_equal_or_files_same(const char *a, const char *b, int flags) {
|
||||
return path_equal(a, b) || files_same(a, b, flags) > 0;
|
||||
}
|
||||
|
||||
char* path_join(const char *root, const char *path, const char *rest) {
|
||||
assert(path);
|
||||
char* path_join_many_internal(const char *first, ...) {
|
||||
char *joined, *q;
|
||||
const char *p;
|
||||
va_list ap;
|
||||
bool slash;
|
||||
size_t sz;
|
||||
|
||||
if (!isempty(root))
|
||||
return strjoin(root, endswith(root, "/") ? "" : "/",
|
||||
path[0] == '/' ? path+1 : path,
|
||||
rest ? (endswith(path, "/") ? "" : "/") : NULL,
|
||||
rest && rest[0] == '/' ? rest+1 : rest);
|
||||
else
|
||||
return strjoin(path,
|
||||
rest ? (endswith(path, "/") ? "" : "/") : NULL,
|
||||
rest && rest[0] == '/' ? rest+1 : rest);
|
||||
assert(first);
|
||||
|
||||
/* Joins all listed strings until NULL and places an "/" between them unless the strings end/begin already with
|
||||
* one so that it is unnecessary. Note that "/" which are already duplicate won't be removed. The string
|
||||
* returned is hence always equal or longer than the sum of the lengths of each individual string.
|
||||
*
|
||||
* Note: any listed empty string is simply skipped. This can be useful for concatenating strings of which some
|
||||
* are optional.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* path_join_many("foo", "bar") → "foo/bar"
|
||||
* path_join_many("foo/", "bar") → "foo/bar"
|
||||
* path_join_many("", "foo", "", "bar", "") → "foo/bar" */
|
||||
|
||||
sz = strlen(first);
|
||||
va_start(ap, first);
|
||||
while ((p = va_arg(ap, char*))) {
|
||||
|
||||
if (*p == 0) /* Skip empty items */
|
||||
continue;
|
||||
|
||||
sz += 1 + strlen(p);
|
||||
}
|
||||
va_end(ap);
|
||||
|
||||
joined = new(char, sz + 1);
|
||||
if (!joined)
|
||||
return NULL;
|
||||
|
||||
if (first[0] != 0) {
|
||||
q = stpcpy(joined, first);
|
||||
slash = endswith(first, "/");
|
||||
} else {
|
||||
/* Skip empty items */
|
||||
joined[0] = 0;
|
||||
q = joined;
|
||||
slash = true; /* no need to generate a slash anymore */
|
||||
}
|
||||
|
||||
va_start(ap, first);
|
||||
while ((p = va_arg(ap, char*))) {
|
||||
|
||||
if (*p == 0) /* Skip empty items */
|
||||
continue;
|
||||
|
||||
if (!slash && p[0] != '/')
|
||||
*(q++) = '/';
|
||||
|
||||
q = stpcpy(q, p);
|
||||
slash = endswith(p, "/");
|
||||
}
|
||||
va_end(ap);
|
||||
|
||||
return joined;
|
||||
}
|
||||
|
||||
int find_binary(const char *name, char **ret) {
|
||||
|
@ -49,7 +49,14 @@ char* path_startswith(const char *path, const char *prefix) _pure_;
|
||||
int path_compare(const char *a, const char *b) _pure_;
|
||||
bool path_equal(const char *a, const char *b) _pure_;
|
||||
bool path_equal_or_files_same(const char *a, const char *b, int flags);
|
||||
char* path_join(const char *root, const char *path, const char *rest);
|
||||
char* path_join_many_internal(const char *first, ...) _sentinel_;
|
||||
#define path_join_many(x, ...) path_join_many_internal(x, __VA_ARGS__, NULL)
|
||||
static inline char* path_join(const char *root, const char *path, const char *rest) {
|
||||
assert(path);
|
||||
|
||||
return path_join_many(strempty(root), path, rest);
|
||||
}
|
||||
|
||||
char* path_simplify(char *path, bool kill_dots);
|
||||
|
||||
static inline bool path_equal_ptr(const char *a, const char *b) {
|
||||
|
@ -10,11 +10,13 @@
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "dirent-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "macro.h"
|
||||
#include "missing.h"
|
||||
#include "parse-util.h"
|
||||
#include "stat-util.h"
|
||||
#include "string-util.h"
|
||||
|
||||
@ -319,3 +321,99 @@ int fd_verify_directory(int fd) {
|
||||
|
||||
return stat_verify_directory(&st);
|
||||
}
|
||||
|
||||
int device_path_make_major_minor(mode_t mode, dev_t devno, char **ret) {
|
||||
const char *t;
|
||||
|
||||
/* Generates the /dev/{char|block}/MAJOR:MINOR path for a dev_t */
|
||||
|
||||
if (S_ISCHR(mode))
|
||||
t = "char";
|
||||
else if (S_ISBLK(mode))
|
||||
t = "block";
|
||||
else
|
||||
return -ENODEV;
|
||||
|
||||
if (asprintf(ret, "/dev/%s/%u:%u", t, major(devno), minor(devno)) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
int device_path_make_canonical(mode_t mode, dev_t devno, char **ret) {
|
||||
_cleanup_free_ char *p = NULL;
|
||||
int r;
|
||||
|
||||
/* Finds the canonical path for a device, i.e. resolves the /dev/{char|block}/MAJOR:MINOR path to the end. */
|
||||
|
||||
assert(ret);
|
||||
|
||||
if (major(devno) == 0 && minor(devno) == 0) {
|
||||
char *s;
|
||||
|
||||
/* A special hack to make sure our 'inaccessible' device nodes work. They won't have symlinks in
|
||||
* /dev/block/ and /dev/char/, hence we handle them specially here. */
|
||||
|
||||
if (S_ISCHR(mode))
|
||||
s = strdup("/run/systemd/inaccessible/chr");
|
||||
else if (S_ISBLK(mode))
|
||||
s = strdup("/run/systemd/inaccessible/blk");
|
||||
else
|
||||
return -ENODEV;
|
||||
|
||||
if (!s)
|
||||
return -ENOMEM;
|
||||
|
||||
*ret = s;
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = device_path_make_major_minor(mode, devno, &p);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return chase_symlinks(p, NULL, 0, ret);
|
||||
}
|
||||
|
||||
int device_path_parse_major_minor(const char *path, mode_t *ret_mode, dev_t *ret_devno) {
|
||||
mode_t mode;
|
||||
dev_t devno;
|
||||
int r;
|
||||
|
||||
/* Tries to extract the major/minor directly from the device path if we can. Handles /dev/block/ and /dev/char/
|
||||
* paths, as well out synthetic inaccessible device nodes. Never goes to disk. Returns -ENODEV if the device
|
||||
* path cannot be parsed like this. */
|
||||
|
||||
if (path_equal(path, "/run/systemd/inaccessible/chr")) {
|
||||
mode = S_IFCHR;
|
||||
devno = makedev(0, 0);
|
||||
} else if (path_equal(path, "/run/systemd/inaccessible/blk")) {
|
||||
mode = S_IFBLK;
|
||||
devno = makedev(0, 0);
|
||||
} else {
|
||||
const char *w;
|
||||
|
||||
w = path_startswith(path, "/dev/block/");
|
||||
if (w)
|
||||
mode = S_IFBLK;
|
||||
else {
|
||||
w = path_startswith(path, "/dev/char/");
|
||||
if (!w)
|
||||
return -ENODEV;
|
||||
|
||||
mode = S_IFCHR;
|
||||
}
|
||||
|
||||
r = parse_dev(w, &devno);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (ret_mode)
|
||||
*ret_mode = mode;
|
||||
if (ret_devno)
|
||||
*ret_devno = devno;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -62,3 +62,26 @@ int fd_verify_regular(int fd);
|
||||
|
||||
int stat_verify_directory(const struct stat *st);
|
||||
int fd_verify_directory(int fd);
|
||||
|
||||
/* glibc and the Linux kernel have different ideas about the major/minor size. These calls will check whether the
|
||||
* specified major is valid by the Linux kernel's standards, not by glibc's. Linux has 20bits of minor, and 12 bits of
|
||||
* major space. See MINORBITS in linux/kdev_t.h in the kernel sources. (If you wonder why we define _y here, instead of
|
||||
* comparing directly >= 0: it's to trick out -Wtype-limits, which would otherwise complain if the type is unsigned, as
|
||||
* such a test would be pointless in such a case.) */
|
||||
|
||||
#define DEVICE_MAJOR_VALID(x) \
|
||||
({ \
|
||||
typeof(x) _x = (x), _y = 0; \
|
||||
_x >= _y && _x < (UINT32_C(1) << 12); \
|
||||
\
|
||||
})
|
||||
|
||||
#define DEVICE_MINOR_VALID(x) \
|
||||
({ \
|
||||
typeof(x) _x = (x), _y = 0; \
|
||||
_x >= _y && _x < (UINT32_C(1) << 20); \
|
||||
})
|
||||
|
||||
int device_path_make_major_minor(mode_t mode, dev_t devno, char **ret);
|
||||
int device_path_make_canonical(mode_t mode, dev_t devno, char **ret);
|
||||
int device_path_parse_major_minor(const char *path, mode_t *ret_mode, dev_t *ret_devno);
|
||||
|
@ -979,53 +979,56 @@ int get_ctty_devnr(pid_t pid, dev_t *d) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int get_ctty(pid_t pid, dev_t *_devnr, char **r) {
|
||||
char fn[STRLEN("/dev/char/") + 2*DECIMAL_STR_MAX(unsigned) + 1 + 1], *b = NULL;
|
||||
_cleanup_free_ char *s = NULL;
|
||||
const char *p;
|
||||
int get_ctty(pid_t pid, dev_t *ret_devnr, char **ret) {
|
||||
_cleanup_free_ char *fn = NULL, *b = NULL;
|
||||
dev_t devnr;
|
||||
int k;
|
||||
int r;
|
||||
|
||||
assert(r);
|
||||
r = get_ctty_devnr(pid, &devnr);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
k = get_ctty_devnr(pid, &devnr);
|
||||
if (k < 0)
|
||||
return k;
|
||||
r = device_path_make_canonical(S_IFCHR, devnr, &fn);
|
||||
if (r < 0) {
|
||||
if (r != -ENOENT) /* No symlink for this in /dev/char/? */
|
||||
return r;
|
||||
|
||||
sprintf(fn, "/dev/char/%u:%u", major(devnr), minor(devnr));
|
||||
|
||||
k = readlink_malloc(fn, &s);
|
||||
if (k < 0) {
|
||||
|
||||
if (k != -ENOENT)
|
||||
return k;
|
||||
|
||||
/* This is an ugly hack */
|
||||
if (major(devnr) == 136) {
|
||||
/* This is an ugly hack: PTY devices are not listed in /dev/char/, as they don't follow the
|
||||
* Linux device model. This means we have no nice way to match them up against their actual
|
||||
* device node. Let's hence do the check by the fixed, assigned major number. Normally we try
|
||||
* to avoid such fixed major/minor matches, but there appears to nother nice way to handle
|
||||
* this. */
|
||||
|
||||
if (asprintf(&b, "pts/%u", minor(devnr)) < 0)
|
||||
return -ENOMEM;
|
||||
} else {
|
||||
/* Probably something like the ptys which have no
|
||||
* symlink in /dev/char. Let's return something
|
||||
* vaguely useful. */
|
||||
/* Probably something similar to the ptys which have no symlink in /dev/char/. Let's return
|
||||
* something vaguely useful. */
|
||||
|
||||
b = strdup(fn + 5);
|
||||
if (!b)
|
||||
return -ENOMEM;
|
||||
r = device_path_make_major_minor(S_IFCHR, devnr, &fn);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
} else {
|
||||
p = PATH_STARTSWITH_SET(s, "/dev/", "../");
|
||||
if (!p)
|
||||
p = s;
|
||||
|
||||
b = strdup(p);
|
||||
if (!b)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
*r = b;
|
||||
if (_devnr)
|
||||
*_devnr = devnr;
|
||||
if (!b) {
|
||||
const char *w;
|
||||
|
||||
w = path_startswith(fn, "/dev/");
|
||||
if (w) {
|
||||
b = strdup(w);
|
||||
if (!b)
|
||||
return -ENOMEM;
|
||||
} else
|
||||
b = TAKE_PTR(fn);
|
||||
}
|
||||
|
||||
if (ret)
|
||||
*ret = TAKE_PTR(b);
|
||||
|
||||
if (ret_devnr)
|
||||
*ret_devnr = devnr;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -84,6 +84,32 @@ int cgroup_bpf_whitelist_major(BPFProgram *prog, int type, int major, const char
|
||||
return r;
|
||||
}
|
||||
|
||||
int cgroup_bpf_whitelist_class(BPFProgram *prog, int type, const char *acc) {
|
||||
struct bpf_insn insn[] = {
|
||||
BPF_JMP_IMM(BPF_JNE, BPF_REG_2, type, 5), /* compare device type */
|
||||
BPF_MOV32_REG(BPF_REG_1, BPF_REG_3), /* calculate access type */
|
||||
BPF_ALU32_IMM(BPF_AND, BPF_REG_1, 0),
|
||||
BPF_JMP_REG(BPF_JNE, BPF_REG_1, BPF_REG_3, 1), /* compare access type */
|
||||
BPF_JMP_A(PASS_JUMP_OFF), /* jump to PASS */
|
||||
};
|
||||
int r, access;
|
||||
|
||||
assert(prog);
|
||||
assert(acc);
|
||||
|
||||
access = bpf_access_type(acc);
|
||||
if (access <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
insn[2].imm = access;
|
||||
|
||||
r = bpf_program_add_instructions(prog, insn, ELEMENTSOF(insn));
|
||||
if (r < 0)
|
||||
log_error_errno(r, "Extending device control BPF program failed: %m");
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int cgroup_init_device_bpf(BPFProgram **ret, CGroupDevicePolicy policy, bool whitelist) {
|
||||
struct bpf_insn pre_insn[] = {
|
||||
/* load device type to r2 */
|
||||
|
@ -11,6 +11,7 @@ int bpf_devices_supported(void);
|
||||
|
||||
int cgroup_bpf_whitelist_device(BPFProgram *p, int type, int major, int minor, const char *acc);
|
||||
int cgroup_bpf_whitelist_major(BPFProgram *p, int type, int major, const char *acc);
|
||||
int cgroup_bpf_whitelist_class(BPFProgram *prog, int type, const char *acc);
|
||||
|
||||
int cgroup_init_device_bpf(BPFProgram **ret, CGroupDevicePolicy policy, bool whitelist);
|
||||
int cgroup_apply_device_bpf(Unit *u, BPFProgram *p, CGroupDevicePolicy policy, bool whitelist);
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "process-util.h"
|
||||
#include "procfs-util.h"
|
||||
#include "special.h"
|
||||
#include "stat-util.h"
|
||||
#include "stdio-util.h"
|
||||
#include "string-table.h"
|
||||
#include "string-util.h"
|
||||
@ -375,16 +376,23 @@ int cgroup_add_device_allow(CGroupContext *c, const char *dev, const char *mode)
|
||||
}
|
||||
|
||||
static int lookup_block_device(const char *p, dev_t *ret) {
|
||||
struct stat st;
|
||||
struct stat st = {};
|
||||
int r;
|
||||
|
||||
assert(p);
|
||||
assert(ret);
|
||||
|
||||
if (stat(p, &st) < 0)
|
||||
return log_warning_errno(errno, "Couldn't stat device '%s': %m", p);
|
||||
r = device_path_parse_major_minor(p, &st.st_mode, &st.st_rdev);
|
||||
if (r == -ENODEV) { /* not a parsable device node, need to go to disk */
|
||||
if (stat(p, &st) < 0)
|
||||
return log_warning_errno(errno, "Couldn't stat device '%s': %m", p);
|
||||
} else if (r < 0)
|
||||
return log_warning_errno(r, "Failed to parse major/minor from path '%s': %m", p);
|
||||
|
||||
if (S_ISBLK(st.st_mode))
|
||||
if (S_ISCHR(st.st_mode)) {
|
||||
log_warning("Device node '%s' is a character device, but block device needed.", p);
|
||||
return -ENOTBLK;
|
||||
} else if (S_ISBLK(st.st_mode))
|
||||
*ret = st.st_rdev;
|
||||
else if (major(st.st_dev) != 0)
|
||||
*ret = st.st_dev; /* If this is not a device node then use the block device this file is stored on */
|
||||
@ -408,30 +416,27 @@ static int lookup_block_device(const char *p, dev_t *ret) {
|
||||
}
|
||||
|
||||
static int whitelist_device(BPFProgram *prog, const char *path, const char *node, const char *acc) {
|
||||
struct stat st;
|
||||
bool ignore_notfound;
|
||||
struct stat st = {};
|
||||
int r;
|
||||
|
||||
assert(path);
|
||||
assert(acc);
|
||||
|
||||
if (node[0] == '-') {
|
||||
/* Non-existent paths starting with "-" must be silently ignored */
|
||||
node++;
|
||||
ignore_notfound = true;
|
||||
} else
|
||||
ignore_notfound = false;
|
||||
/* Some special handling for /dev/block/%u:%u, /dev/char/%u:%u, /run/systemd/inaccessible/chr and
|
||||
* /run/systemd/inaccessible/blk paths. Instead of stat()ing these we parse out the major/minor directly. This
|
||||
* means clients can use these path without the device node actually around */
|
||||
r = device_path_parse_major_minor(node, &st.st_mode, &st.st_rdev);
|
||||
if (r < 0) {
|
||||
if (r != -ENODEV)
|
||||
return log_warning_errno(r, "Couldn't parse major/minor from device path '%s': %m", node);
|
||||
|
||||
if (stat(node, &st) < 0) {
|
||||
if (errno == ENOENT && ignore_notfound)
|
||||
return 0;
|
||||
if (stat(node, &st) < 0)
|
||||
return log_warning_errno(errno, "Couldn't stat device %s: %m", node);
|
||||
|
||||
return log_warning_errno(errno, "Couldn't stat device %s: %m", node);
|
||||
}
|
||||
|
||||
if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) {
|
||||
log_warning("%s is not a device.", node);
|
||||
return -ENODEV;
|
||||
if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) {
|
||||
log_warning("%s is not a device.", node);
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
if (cg_all_unified() > 0) {
|
||||
@ -463,21 +468,64 @@ static int whitelist_device(BPFProgram *prog, const char *path, const char *node
|
||||
|
||||
static int whitelist_major(BPFProgram *prog, const char *path, const char *name, char type, const char *acc) {
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
char *p, *w;
|
||||
char buf[2+DECIMAL_STR_MAX(unsigned)+3+4];
|
||||
bool good = false;
|
||||
unsigned maj;
|
||||
int r;
|
||||
|
||||
assert(path);
|
||||
assert(acc);
|
||||
assert(IN_SET(type, 'b', 'c'));
|
||||
|
||||
if (streq(name, "*")) {
|
||||
/* If the name is a wildcard, then apply this list to all devices of this type */
|
||||
|
||||
if (cg_all_unified() > 0) {
|
||||
if (!prog)
|
||||
return 0;
|
||||
|
||||
(void) cgroup_bpf_whitelist_class(prog, type == 'c' ? BPF_DEVCG_DEV_CHAR : BPF_DEVCG_DEV_BLOCK, acc);
|
||||
} else {
|
||||
xsprintf(buf, "%c *:* %s", type, acc);
|
||||
|
||||
r = cg_set_attribute("devices", path, "devices.allow", buf);
|
||||
if (r < 0)
|
||||
log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EINVAL, -EACCES) ? LOG_DEBUG : LOG_WARNING, r,
|
||||
"Failed to set devices.allow on %s: %m", path);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (safe_atou(name, &maj) >= 0 && DEVICE_MAJOR_VALID(maj)) {
|
||||
/* The name is numeric and suitable as major. In that case, let's take is major, and create the entry
|
||||
* directly */
|
||||
|
||||
if (cg_all_unified() > 0) {
|
||||
if (!prog)
|
||||
return 0;
|
||||
|
||||
(void) cgroup_bpf_whitelist_major(prog,
|
||||
type == 'c' ? BPF_DEVCG_DEV_CHAR : BPF_DEVCG_DEV_BLOCK,
|
||||
maj, acc);
|
||||
} else {
|
||||
xsprintf(buf, "%c %u:* %s", type, maj, acc);
|
||||
|
||||
r = cg_set_attribute("devices", path, "devices.allow", buf);
|
||||
if (r < 0)
|
||||
log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EINVAL, -EACCES) ? LOG_DEBUG : LOG_WARNING, r,
|
||||
"Failed to set devices.allow on %s: %m", path);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
f = fopen("/proc/devices", "re");
|
||||
if (!f)
|
||||
return log_warning_errno(errno, "Cannot open /proc/devices to resolve %s (%c): %m", name, type);
|
||||
|
||||
for (;;) {
|
||||
_cleanup_free_ char *line = NULL;
|
||||
unsigned maj;
|
||||
char *w, *p;
|
||||
|
||||
r = read_line(f, LONG_LINE_MAX, &line);
|
||||
if (r < 0)
|
||||
@ -530,8 +578,6 @@ static int whitelist_major(BPFProgram *prog, const char *path, const char *name,
|
||||
type == 'c' ? BPF_DEVCG_DEV_CHAR : BPF_DEVCG_DEV_BLOCK,
|
||||
maj, acc);
|
||||
} else {
|
||||
char buf[2+DECIMAL_STR_MAX(unsigned)+3+4];
|
||||
|
||||
sprintf(buf,
|
||||
"%c %u:* %s",
|
||||
type,
|
||||
@ -1098,8 +1144,8 @@ static void cgroup_context_apply(
|
||||
"/dev/tty\0" "rwm\0"
|
||||
"/dev/ptmx\0" "rwm\0"
|
||||
/* Allow /run/systemd/inaccessible/{chr,blk} devices for mapping InaccessiblePaths */
|
||||
"-/run/systemd/inaccessible/chr\0" "rwm\0"
|
||||
"-/run/systemd/inaccessible/blk\0" "rwm\0";
|
||||
"/run/systemd/inaccessible/chr\0" "rwm\0"
|
||||
"/run/systemd/inaccessible/blk\0" "rwm\0";
|
||||
|
||||
const char *x, *y;
|
||||
|
||||
@ -1133,7 +1179,7 @@ static void cgroup_context_apply(
|
||||
else if ((val = startswith(a->path, "char-")))
|
||||
(void) whitelist_major(prog, path, val, 'c', acc);
|
||||
else
|
||||
log_unit_debug(u, "Ignoring device %s while writing cgroup attribute.", a->path);
|
||||
log_unit_debug(u, "Ignoring device '%s' while writing cgroup attribute.", a->path);
|
||||
}
|
||||
|
||||
r = cgroup_apply_device_bpf(u, prog, c->device_policy, c->device_allow);
|
||||
|
@ -460,20 +460,9 @@ int mount_setup(bool loaded_policy) {
|
||||
(void) mkdir_label("/run/systemd", 0755);
|
||||
(void) mkdir_label("/run/systemd/system", 0755);
|
||||
|
||||
/* Set up inaccessible (and empty) file nodes of all types */
|
||||
(void) mkdir_label("/run/systemd/inaccessible", 0000);
|
||||
(void) mknod("/run/systemd/inaccessible/reg", S_IFREG | 0000, 0);
|
||||
(void) mkdir_label("/run/systemd/inaccessible/dir", 0000);
|
||||
(void) mkfifo("/run/systemd/inaccessible/fifo", 0000);
|
||||
(void) mknod("/run/systemd/inaccessible/sock", S_IFSOCK | 0000, 0);
|
||||
|
||||
/* The following two are likely to fail if we lack the privs for it (for example in an userns environment, if
|
||||
* CAP_SYS_MKNOD is missing, or if a device node policy prohibit major/minor of 0 device nodes to be
|
||||
* created). But that's entirely fine. Consumers of these files should carry fallback to use a different node
|
||||
* then, for example /run/systemd/inaccessible/sock, which is close enough in behaviour and semantics for most
|
||||
* uses. */
|
||||
(void) mknod("/run/systemd/inaccessible/chr", S_IFCHR | 0000, makedev(0, 0));
|
||||
(void) mknod("/run/systemd/inaccessible/blk", S_IFBLK | 0000, makedev(0, 0));
|
||||
/* Also create /run/systemd/inaccessible nodes, so that we always have something to mount inaccessible nodes
|
||||
* from. */
|
||||
(void) make_inaccessible_nodes(NULL, UID_INVALID, GID_INVALID);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -594,16 +594,17 @@ _public_ int sd_device_new_from_device_id(sd_device **ret, const char *id) {
|
||||
|
||||
switch (id[0]) {
|
||||
case 'b':
|
||||
case 'c':
|
||||
{
|
||||
char type;
|
||||
int maj, min;
|
||||
case 'c': {
|
||||
dev_t devt;
|
||||
|
||||
r = sscanf(id, "%c%i:%i", &type, &maj, &min);
|
||||
if (r != 3)
|
||||
if (isempty(id))
|
||||
return -EINVAL;
|
||||
|
||||
return sd_device_new_from_devnum(ret, type, makedev(maj, min));
|
||||
r = parse_dev(id + 1, &devt);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return sd_device_new_from_devnum(ret, id[0], devt);
|
||||
}
|
||||
case 'n':
|
||||
{
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "logind-session.h"
|
||||
#include "logind.h"
|
||||
#include "signal-util.h"
|
||||
#include "stat-util.h"
|
||||
#include "strv.h"
|
||||
#include "util.h"
|
||||
|
||||
@ -380,6 +381,9 @@ static int method_take_device(sd_bus_message *message, void *userdata, sd_bus_er
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!DEVICE_MAJOR_VALID(major) || !DEVICE_MINOR_VALID(minor))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Device major/minor is not valid.");
|
||||
|
||||
if (!session_is_controller(s, sd_bus_message_get_sender(message)))
|
||||
return sd_bus_error_setf(error, BUS_ERROR_NOT_IN_CONTROL, "You are not in control of this session");
|
||||
|
||||
@ -427,6 +431,9 @@ static int method_release_device(sd_bus_message *message, void *userdata, sd_bus
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!DEVICE_MAJOR_VALID(major) || !DEVICE_MINOR_VALID(minor))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Device major/minor is not valid.");
|
||||
|
||||
if (!session_is_controller(s, sd_bus_message_get_sender(message)))
|
||||
return sd_bus_error_setf(error, BUS_ERROR_NOT_IN_CONTROL, "You are not in control of this session");
|
||||
|
||||
@ -455,6 +462,9 @@ static int method_pause_device_complete(sd_bus_message *message, void *userdata,
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!DEVICE_MAJOR_VALID(major) || !DEVICE_MINOR_VALID(minor))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Device major/minor is not valid.");
|
||||
|
||||
if (!session_is_controller(s, sd_bus_message_get_sender(message)))
|
||||
return sd_bus_error_setf(error, BUS_ERROR_NOT_IN_CONTROL, "You are not in control of this session");
|
||||
|
||||
|
@ -557,6 +557,8 @@ int mount_all(const char *dest,
|
||||
MOUNT_FATAL },
|
||||
{ "tmpfs", "/run", "tmpfs", "mode=755", MS_NOSUID|MS_NODEV|MS_STRICTATIME,
|
||||
MOUNT_FATAL },
|
||||
{ "mqueue", "/dev/mqueue", "mqueue", NULL, 0,
|
||||
MOUNT_FATAL },
|
||||
|
||||
#if HAVE_SELINUX
|
||||
{ "/sys/fs/selinux", "/sys/fs/selinux", NULL, NULL, MS_BIND,
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
#include "pretty-print.h"
|
||||
#include "stat-util.h"
|
||||
#include "strv.h"
|
||||
|
||||
static const char *arg_target = NULL;
|
||||
@ -69,13 +70,16 @@ static int resize_btrfs(const char *path, int mountfd, int devfd, uint64_t numbl
|
||||
|
||||
#if HAVE_LIBCRYPTSETUP
|
||||
static int resize_crypt_luks_device(dev_t devno, const char *fstype, dev_t main_devno) {
|
||||
char devpath[DEV_NUM_PATH_MAX], main_devpath[DEV_NUM_PATH_MAX];
|
||||
_cleanup_close_ int main_devfd = -1;
|
||||
_cleanup_free_ char *devpath = NULL, *main_devpath = NULL;
|
||||
_cleanup_(crypt_freep) struct crypt_device *cd = NULL;
|
||||
_cleanup_close_ int main_devfd = -1;
|
||||
uint64_t size;
|
||||
int r;
|
||||
|
||||
xsprintf_dev_num_path(main_devpath, "block", main_devno);
|
||||
r = device_path_make_major_minor(S_IFBLK, main_devno, &main_devpath);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to format device major/minor path: %m");
|
||||
|
||||
main_devfd = open(main_devpath, O_RDONLY|O_CLOEXEC);
|
||||
if (main_devfd < 0)
|
||||
return log_error_errno(errno, "Failed to open \"%s\": %m", main_devpath);
|
||||
@ -85,8 +89,10 @@ static int resize_crypt_luks_device(dev_t devno, const char *fstype, dev_t main_
|
||||
main_devpath);
|
||||
|
||||
log_debug("%s is %"PRIu64" bytes", main_devpath, size);
|
||||
r = device_path_make_major_minor(S_IFBLK, devno, &devpath);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to format major/minor path: %m");
|
||||
|
||||
xsprintf_dev_num_path(devpath, "block", devno);
|
||||
r = crypt_init(&cd, devpath);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "crypt_init(\"%s\") failed: %m", devpath);
|
||||
@ -115,9 +121,8 @@ static int resize_crypt_luks_device(dev_t devno, const char *fstype, dev_t main_
|
||||
#endif
|
||||
|
||||
static int maybe_resize_slave_device(const char *mountpath, dev_t main_devno) {
|
||||
_cleanup_free_ char *fstype = NULL, *devpath = NULL;
|
||||
dev_t devno;
|
||||
char devpath[DEV_NUM_PATH_MAX];
|
||||
_cleanup_free_ char *fstype = NULL;
|
||||
int r;
|
||||
|
||||
#if HAVE_LIBCRYPTSETUP
|
||||
@ -137,7 +142,10 @@ static int maybe_resize_slave_device(const char *mountpath, dev_t main_devno) {
|
||||
if (devno == main_devno)
|
||||
return 0;
|
||||
|
||||
xsprintf_dev_num_path(devpath, "block", devno);
|
||||
r = device_path_make_major_minor(S_IFBLK, devno, &devpath);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to format device major/minor path: %m");
|
||||
|
||||
r = probe_filesystem(devpath, &fstype);
|
||||
if (r == -EUCLEAN)
|
||||
return log_warning_errno(r, "Cannot reliably determine probe \"%s\", refusing to proceed.", devpath);
|
||||
@ -222,12 +230,13 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
dev_t devno;
|
||||
_cleanup_close_ int mountfd = -1, devfd = -1;
|
||||
int blocksize;
|
||||
_cleanup_free_ char *devpath = NULL;
|
||||
uint64_t size, numblocks;
|
||||
char devpath[DEV_NUM_PATH_MAX], fb[FORMAT_BYTES_MAX];
|
||||
char fb[FORMAT_BYTES_MAX];
|
||||
struct statfs sfs;
|
||||
dev_t devno;
|
||||
int blocksize;
|
||||
int r;
|
||||
|
||||
log_setup_service();
|
||||
@ -264,7 +273,12 @@ int main(int argc, char *argv[]) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
xsprintf_dev_num_path(devpath, "block", devno);
|
||||
r = device_path_make_major_minor(S_IFBLK, devno, &devpath);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to format device major/minor path: %m");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
devfd = open(devpath, O_RDONLY|O_CLOEXEC);
|
||||
if (devfd < 0) {
|
||||
log_error_errno(errno, "Failed to open \"%s\": %m", devpath);
|
||||
|
@ -405,7 +405,7 @@ static int verify_esp(
|
||||
sd_id128_t *ret_uuid) {
|
||||
#if HAVE_BLKID
|
||||
_cleanup_(blkid_free_probep) blkid_probe b = NULL;
|
||||
char t[DEV_NUM_PATH_MAX];
|
||||
_cleanup_free_ char *node = NULL;
|
||||
const char *v;
|
||||
#endif
|
||||
uint64_t pstart = 0, psize = 0;
|
||||
@ -469,9 +469,11 @@ static int verify_esp(
|
||||
goto finish;
|
||||
|
||||
#if HAVE_BLKID
|
||||
xsprintf_dev_num_path(t, "block", st.st_dev);
|
||||
r = device_path_make_major_minor(S_IFBLK, st.st_dev, &node);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to format major/minor device path: %m");
|
||||
errno = 0;
|
||||
b = blkid_new_probe_from_filename(t);
|
||||
b = blkid_new_probe_from_filename(node);
|
||||
if (!b)
|
||||
return log_error_errno(errno ?: ENOMEM, "Failed to open file system \"%s\": %m", p);
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "label.h"
|
||||
#include "log.h"
|
||||
#include "path-util.h"
|
||||
#include "umask-util.h"
|
||||
#include "user-util.h"
|
||||
#include "util.h"
|
||||
|
||||
@ -54,3 +55,61 @@ int dev_setup(const char *prefix, uid_t uid, gid_t gid) {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int make_inaccessible_nodes(const char *root, uid_t uid, gid_t gid) {
|
||||
static const struct {
|
||||
const char *name;
|
||||
mode_t mode;
|
||||
} table[] = {
|
||||
{ "/run/systemd", S_IFDIR | 0755 },
|
||||
{ "/run/systemd/inaccessible", S_IFDIR | 0000 },
|
||||
{ "/run/systemd/inaccessible/reg", S_IFREG | 0000 },
|
||||
{ "/run/systemd/inaccessible/dir", S_IFDIR | 0000 },
|
||||
{ "/run/systemd/inaccessible/fifo", S_IFIFO | 0000 },
|
||||
{ "/run/systemd/inaccessible/sock", S_IFSOCK | 0000 },
|
||||
|
||||
/* The following two are likely to fail if we lack the privs for it (for example in an userns
|
||||
* environment, if CAP_SYS_MKNOD is missing, or if a device node policy prohibit major/minor of 0
|
||||
* device nodes to be created). But that's entirely fine. Consumers of these files should carry
|
||||
* fallback to use a different node then, for example /run/systemd/inaccessible/sock, which is close
|
||||
* enough in behaviour and semantics for most uses. */
|
||||
{ "/run/systemd/inaccessible/chr", S_IFCHR | 0000 },
|
||||
{ "/run/systemd/inaccessible/blk", S_IFBLK | 0000 },
|
||||
};
|
||||
|
||||
_cleanup_umask_ mode_t u;
|
||||
size_t i;
|
||||
int r;
|
||||
|
||||
u = umask(0000);
|
||||
|
||||
/* Set up inaccessible (and empty) file nodes of all types. This are used to as mount sources for over-mounting
|
||||
* ("masking") file nodes that shall become inaccessible and empty for specific containers or services. We try
|
||||
* to lock down these nodes as much as we can, but otherwise try to match them as closely as possible with the
|
||||
* underlying file, i.e. in the best case we offer the same node type as the underlying node. */
|
||||
|
||||
for (i = 0; i < ELEMENTSOF(table); i++) {
|
||||
_cleanup_free_ char *path = NULL;
|
||||
|
||||
path = prefix_root(root, table[i].name);
|
||||
if (!path)
|
||||
return log_oom();
|
||||
|
||||
if (S_ISDIR(table[i].mode))
|
||||
r = mkdir(path, table[i].mode & 07777);
|
||||
else
|
||||
r = mknod(path, table[i].mode, makedev(0, 0));
|
||||
if (r < 0) {
|
||||
if (errno != EEXIST)
|
||||
log_debug_errno(errno, "Failed to create '%s', ignoring: %m", path);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (uid != UID_INVALID || gid != GID_INVALID) {
|
||||
if (lchown(path, uid, gid) < 0)
|
||||
log_debug_errno(errno, "Failed to chown '%s': %m", path);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -4,3 +4,5 @@
|
||||
#include <sys/types.h>
|
||||
|
||||
int dev_setup(const char *prefix, uid_t uid, gid_t gid);
|
||||
|
||||
int make_inaccessible_nodes(const char *root, uid_t uid, gid_t gid);
|
||||
|
@ -220,8 +220,9 @@ int dissect_image(
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (asprintf(&n, "/dev/block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0)
|
||||
return -ENOMEM;
|
||||
r = device_path_make_major_minor(st.st_mode, st.st_rdev, &n);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
m->partitions[PARTITION_ROOT] = (DissectedPartition) {
|
||||
.found = true,
|
||||
|
@ -392,11 +392,14 @@ int pty_forward_new(
|
||||
struct winsize ws;
|
||||
int r;
|
||||
|
||||
f = new0(PTYForward, 1);
|
||||
f = new(PTYForward, 1);
|
||||
if (!f)
|
||||
return -ENOMEM;
|
||||
|
||||
f->flags = flags;
|
||||
*f = (struct PTYForward) {
|
||||
.flags = flags,
|
||||
.master = -1,
|
||||
};
|
||||
|
||||
if (event)
|
||||
f->event = sd_event_ref(event);
|
||||
@ -587,3 +590,42 @@ int pty_forward_set_priority(PTYForward *f, int64_t priority) {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pty_forward_set_width_height(PTYForward *f, unsigned width, unsigned height) {
|
||||
struct winsize ws;
|
||||
|
||||
assert(f);
|
||||
|
||||
if (width == (unsigned) -1 && height == (unsigned) -1)
|
||||
return 0; /* noop */
|
||||
|
||||
if (width != (unsigned) -1 &&
|
||||
(width == 0 || width > USHRT_MAX))
|
||||
return -ERANGE;
|
||||
|
||||
if (height != (unsigned) -1 &&
|
||||
(height == 0 || height > USHRT_MAX))
|
||||
return -ERANGE;
|
||||
|
||||
if (width == (unsigned) -1 || height == (unsigned) -1) {
|
||||
if (ioctl(f->master, TIOCGWINSZ, &ws) < 0)
|
||||
return -errno;
|
||||
|
||||
if (width != (unsigned) -1)
|
||||
ws.ws_col = width;
|
||||
if (height != (unsigned) -1)
|
||||
ws.ws_row = height;
|
||||
} else
|
||||
ws = (struct winsize) {
|
||||
.ws_row = height,
|
||||
.ws_col = width,
|
||||
};
|
||||
|
||||
if (ioctl(f->master, TIOCSWINSZ, &ws) < 0)
|
||||
return -errno;
|
||||
|
||||
/* Make sure we ignore SIGWINCH window size events from now on */
|
||||
f->sigwinch_event_source = sd_event_source_unref(f->sigwinch_event_source);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -37,4 +37,6 @@ bool pty_forward_drain(PTYForward *f);
|
||||
|
||||
int pty_forward_set_priority(PTYForward *f, int64_t priority);
|
||||
|
||||
int pty_forward_set_width_height(PTYForward *f, unsigned width, unsigned height);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(PTYForward*, pty_forward_free);
|
||||
|
@ -156,6 +156,10 @@ tests += [
|
||||
[],
|
||||
[]],
|
||||
|
||||
[['src/test/test-dev-setup.c'],
|
||||
[],
|
||||
[]],
|
||||
|
||||
[['src/test/test-capability.c'],
|
||||
[],
|
||||
[libcap]],
|
||||
|
62
src/test/test-dev-setup.c
Normal file
62
src/test/test-dev-setup.c
Normal file
@ -0,0 +1,62 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include "capability-util.h"
|
||||
#include "dev-setup.h"
|
||||
#include "fileio.h"
|
||||
#include "fs-util.h"
|
||||
#include "path-util.h"
|
||||
#include "rm-rf.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
_cleanup_(rm_rf_physical_and_freep) char *p = NULL;
|
||||
const char *f;
|
||||
struct stat st;
|
||||
|
||||
if (have_effective_cap(CAP_DAC_OVERRIDE) <= 0)
|
||||
return EXIT_TEST_SKIP;
|
||||
|
||||
assert_se(mkdtemp_malloc("/tmp/test-dev-setupXXXXXX", &p) >= 0);
|
||||
|
||||
f = prefix_roota(p, "/run");
|
||||
assert_se(mkdir(f, 0755) >= 0);
|
||||
|
||||
assert_se(make_inaccessible_nodes(p, 1, 1) >= 0);
|
||||
|
||||
f = prefix_roota(p, "/run/systemd/inaccessible/reg");
|
||||
assert_se(stat(f, &st) >= 0);
|
||||
assert_se(S_ISREG(st.st_mode));
|
||||
assert_se((st.st_mode & 07777) == 0000);
|
||||
|
||||
f = prefix_roota(p, "/run/systemd/inaccessible/dir");
|
||||
assert_se(stat(f, &st) >= 0);
|
||||
assert_se(S_ISDIR(st.st_mode));
|
||||
assert_se((st.st_mode & 07777) == 0000);
|
||||
|
||||
f = prefix_roota(p, "/run/systemd/inaccessible/fifo");
|
||||
assert_se(stat(f, &st) >= 0);
|
||||
assert_se(S_ISFIFO(st.st_mode));
|
||||
assert_se((st.st_mode & 07777) == 0000);
|
||||
|
||||
f = prefix_roota(p, "/run/systemd/inaccessible/sock");
|
||||
assert_se(stat(f, &st) >= 0);
|
||||
assert_se(S_ISSOCK(st.st_mode));
|
||||
assert_se((st.st_mode & 07777) == 0000);
|
||||
|
||||
f = prefix_roota(p, "/run/systemd/inaccessible/chr");
|
||||
if (stat(f, &st) < 0)
|
||||
assert_se(errno == ENOENT);
|
||||
else {
|
||||
assert_se(S_ISCHR(st.st_mode));
|
||||
assert_se((st.st_mode & 07777) == 0000);
|
||||
}
|
||||
|
||||
f = prefix_roota(p, "/run/systemd/inaccessible/blk");
|
||||
if (stat(f, &st) < 0)
|
||||
assert_se(errno == ENOENT);
|
||||
else {
|
||||
assert_se(S_ISBLK(st.st_mode));
|
||||
assert_se((st.st_mode & 07777) == 0000);
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
@ -726,10 +726,12 @@ static void test_parse_dev(void) {
|
||||
assert_se(parse_dev("5", &dev) == -EINVAL);
|
||||
assert_se(parse_dev("5:", &dev) == -EINVAL);
|
||||
assert_se(parse_dev(":5", &dev) == -EINVAL);
|
||||
assert_se(parse_dev("-1:-1", &dev) == -EINVAL);
|
||||
#if SIZEOF_DEV_T < 8
|
||||
assert_se(parse_dev("4294967295:4294967295", &dev) == -EINVAL);
|
||||
#endif
|
||||
assert_se(parse_dev("8:11", &dev) >= 0 && major(dev) == 8 && minor(dev) == 11);
|
||||
assert_se(parse_dev("0:0", &dev) >= 0 && major(dev) == 0 && minor(dev) == 0);
|
||||
}
|
||||
|
||||
static void test_parse_errno(void) {
|
||||
|
@ -567,6 +567,43 @@ static void test_path_startswith_set(void) {
|
||||
assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo2/bar", "/foo/quux", "", "/zzz"), NULL));
|
||||
}
|
||||
|
||||
static void test_path_join_many(void) {
|
||||
char *j;
|
||||
|
||||
assert_se(streq_ptr(j = path_join_many("", NULL), ""));
|
||||
free(j);
|
||||
|
||||
assert_se(streq_ptr(j = path_join_many("foo", NULL), "foo"));
|
||||
free(j);
|
||||
|
||||
assert_se(streq_ptr(j = path_join_many("foo", "bar"), "foo/bar"));
|
||||
free(j);
|
||||
|
||||
assert_se(streq_ptr(j = path_join_many("", "foo", "", "bar", ""), "foo/bar"));
|
||||
free(j);
|
||||
|
||||
assert_se(streq_ptr(j = path_join_many("", "", "", "", "foo", "", "", "", "bar", "", "", ""), "foo/bar"));
|
||||
free(j);
|
||||
|
||||
assert_se(streq_ptr(j = path_join_many("", "/", "", "/foo/", "", "/", "", "/bar/", "", "/", ""), "//foo///bar//"));
|
||||
free(j);
|
||||
|
||||
assert_se(streq_ptr(j = path_join_many("/", "foo", "/", "bar", "/"), "/foo/bar/"));
|
||||
free(j);
|
||||
|
||||
assert_se(streq_ptr(j = path_join_many("foo", "bar", "baz"), "foo/bar/baz"));
|
||||
free(j);
|
||||
|
||||
assert_se(streq_ptr(j = path_join_many("foo/", "bar", "/baz"), "foo/bar/baz"));
|
||||
free(j);
|
||||
|
||||
assert_se(streq_ptr(j = path_join_many("foo/", "/bar/", "/baz"), "foo//bar//baz"));
|
||||
free(j);
|
||||
|
||||
assert_se(streq_ptr(j = path_join_many("//foo/", "///bar/", "///baz//"), "//foo////bar////baz//"));
|
||||
free(j);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
test_setup_logging(LOG_DEBUG);
|
||||
|
||||
@ -588,6 +625,7 @@ int main(int argc, char **argv) {
|
||||
test_skip_dev_prefix();
|
||||
test_empty_or_root();
|
||||
test_path_startswith_set();
|
||||
test_path_join_many();
|
||||
|
||||
test_systemd_installation_has_version(argv[1]); /* NULL is OK */
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "missing.h"
|
||||
#include "mountpoint-util.h"
|
||||
#include "stat-util.h"
|
||||
#include "path-util.h"
|
||||
|
||||
static void test_files_same(void) {
|
||||
_cleanup_close_ int fd = -1;
|
||||
@ -81,12 +82,87 @@ static void test_fd_is_network_ns(void) {
|
||||
assert_se(IN_SET(fd_is_network_ns(fd), 1, -EUCLEAN));
|
||||
}
|
||||
|
||||
static void test_device_major_minor_valid(void) {
|
||||
/* on glibc dev_t is 64bit, even though in the kernel it is only 32bit */
|
||||
assert_cc(sizeof(dev_t) == sizeof(uint64_t));
|
||||
|
||||
assert_se(DEVICE_MAJOR_VALID(0U));
|
||||
assert_se(DEVICE_MINOR_VALID(0U));
|
||||
|
||||
assert_se(DEVICE_MAJOR_VALID(1U));
|
||||
assert_se(DEVICE_MINOR_VALID(1U));
|
||||
|
||||
assert_se(!DEVICE_MAJOR_VALID(-1U));
|
||||
assert_se(!DEVICE_MINOR_VALID(-1U));
|
||||
|
||||
assert_se(DEVICE_MAJOR_VALID(1U << 10));
|
||||
assert_se(DEVICE_MINOR_VALID(1U << 10));
|
||||
|
||||
assert_se(DEVICE_MAJOR_VALID((1U << 12) - 1));
|
||||
assert_se(DEVICE_MINOR_VALID((1U << 20) - 1));
|
||||
|
||||
assert_se(!DEVICE_MAJOR_VALID((1U << 12)));
|
||||
assert_se(!DEVICE_MINOR_VALID((1U << 20)));
|
||||
|
||||
assert_se(!DEVICE_MAJOR_VALID(1U << 25));
|
||||
assert_se(!DEVICE_MINOR_VALID(1U << 25));
|
||||
|
||||
assert_se(!DEVICE_MAJOR_VALID(UINT32_MAX));
|
||||
assert_se(!DEVICE_MINOR_VALID(UINT32_MAX));
|
||||
|
||||
assert_se(!DEVICE_MAJOR_VALID(UINT64_MAX));
|
||||
assert_se(!DEVICE_MINOR_VALID(UINT64_MAX));
|
||||
|
||||
assert_se(DEVICE_MAJOR_VALID(major(0)));
|
||||
assert_se(DEVICE_MINOR_VALID(minor(0)));
|
||||
}
|
||||
|
||||
static void test_device_path_make_canonical_one(const char *path) {
|
||||
_cleanup_free_ char *resolved = NULL, *raw = NULL;
|
||||
struct stat st;
|
||||
dev_t devno;
|
||||
mode_t mode;
|
||||
int r;
|
||||
|
||||
assert_se(stat(path, &st) >= 0);
|
||||
r = device_path_make_canonical(st.st_mode, st.st_rdev, &resolved);
|
||||
if (r == -ENOENT) /* maybe /dev/char/x:y and /dev/block/x:y are missing in this test environment, because we
|
||||
* run in a container or so? */
|
||||
return;
|
||||
|
||||
assert_se(r >= 0);
|
||||
assert_se(path_equal(path, resolved));
|
||||
|
||||
assert_se(device_path_make_major_minor(st.st_mode, st.st_rdev, &raw) >= 0);
|
||||
assert_se(device_path_parse_major_minor(raw, &mode, &devno) >= 0);
|
||||
|
||||
assert_se(st.st_rdev == devno);
|
||||
assert_se((st.st_mode & S_IFMT) == (mode & S_IFMT));
|
||||
}
|
||||
|
||||
static void test_device_path_make_canonical(void) {
|
||||
|
||||
test_device_path_make_canonical_one("/dev/null");
|
||||
test_device_path_make_canonical_one("/dev/zero");
|
||||
test_device_path_make_canonical_one("/dev/full");
|
||||
test_device_path_make_canonical_one("/dev/random");
|
||||
test_device_path_make_canonical_one("/dev/urandom");
|
||||
test_device_path_make_canonical_one("/dev/tty");
|
||||
|
||||
if (is_device_node("/run/systemd/inaccessible/chr") > 0) {
|
||||
test_device_path_make_canonical_one("/run/systemd/inaccessible/chr");
|
||||
test_device_path_make_canonical_one("/run/systemd/inaccessible/blk");
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
test_files_same();
|
||||
test_is_symlink();
|
||||
test_path_is_fs_type();
|
||||
test_path_is_temporary_fs();
|
||||
test_fd_is_network_ns();
|
||||
test_device_major_minor_valid();
|
||||
test_device_path_make_canonical();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -2636,24 +2636,21 @@ static int parse_line(const char *fname, unsigned line, const char *buffer, bool
|
||||
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:
|
||||
|
Loading…
Reference in New Issue
Block a user