Merge pull request #10996 from poettering/oci-prep

Preparation for the nspawn-OCI work
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2018-11-30 10:09:00 +01:00
committed by GitHub
31 changed files with 922 additions and 147 deletions
+12 -10
View File
@@ -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;
}
+125
View File
@@ -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;
}
+24
View File
@@ -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);
+40 -10
View File
@@ -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;
+20 -6
View File
@@ -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;
}
+61 -11
View File
@@ -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) {
+8 -1
View File
@@ -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) {
+98
View File
@@ -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;
}
+23
View File
@@ -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);
+38 -35
View File
@@ -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;
}
+26
View File
@@ -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 */
+1
View File
@@ -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);
+74 -28
View File
@@ -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);
+3 -14
View File
@@ -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;
}
+8 -7
View File
@@ -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':
{
+10
View File
@@ -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");
+2
View File
@@ -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,
+25 -11
View File
@@ -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);
+5 -3
View File
@@ -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);
+59
View File
@@ -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;
}
+2
View File
@@ -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);
+3 -2
View File
@@ -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,
+44 -2
View File
@@ -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;
}
+2
View File
@@ -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);
+4
View File
@@ -156,6 +156,10 @@ tests += [
[],
[]],
[['src/test/test-dev-setup.c'],
[],
[]],
[['src/test/test-capability.c'],
[],
[libcap]],
+62
View 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;
}
+2
View File
@@ -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) {
+38
View File
@@ -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 */
+76
View File
@@ -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;
}
+4 -7
View File
@@ -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: