diff options
author | Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> | 2018-11-30 10:09:00 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-11-30 10:09:00 +0100 |
commit | b2ac2b01c8ddaabbe63bcb7168dd752f44320d86 (patch) | |
tree | 91b74d45e81f52ee09019fa33e92ab926eac2f5d /src/basic | |
parent | docs: turn LGTM URL into a markdown link (diff) | |
parent | update TODO (diff) | |
download | systemd-b2ac2b01c8ddaabbe63bcb7168dd752f44320d86.tar.xz systemd-b2ac2b01c8ddaabbe63bcb7168dd752f44320d86.zip |
Merge pull request #10996 from poettering/oci-prep
Preparation for the nspawn-OCI work
Diffstat (limited to 'src/basic')
-rw-r--r-- | src/basic/blockdev-util.c | 22 | ||||
-rw-r--r-- | src/basic/capability-util.c | 125 | ||||
-rw-r--r-- | src/basic/capability-util.h | 24 | ||||
-rw-r--r-- | src/basic/fs-util.c | 50 | ||||
-rw-r--r-- | src/basic/parse-util.c | 26 | ||||
-rw-r--r-- | src/basic/path-util.c | 72 | ||||
-rw-r--r-- | src/basic/path-util.h | 9 | ||||
-rw-r--r-- | src/basic/stat-util.c | 98 | ||||
-rw-r--r-- | src/basic/stat-util.h | 23 | ||||
-rw-r--r-- | src/basic/terminal-util.c | 71 |
10 files changed, 448 insertions, 72 deletions
diff --git a/src/basic/blockdev-util.c b/src/basic/blockdev-util.c index 42b311eccd..3017ecd55d 100644 --- a/src/basic/blockdev-util.c +++ b/src/basic/blockdev-util.c @@ -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; } diff --git a/src/basic/capability-util.c b/src/basic/capability-util.c index 6ae35e078b..a3f3ca9f52 100644 --- a/src/basic/capability-util.c +++ b/src/basic/capability-util.c @@ -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; +} diff --git a/src/basic/capability-util.h b/src/basic/capability-util.h index 59591d4b52..e0e0b1c0fa 100644 --- a/src/basic/capability-util.h +++ b/src/basic/capability-util.h @@ -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); diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index 55651baa80..94efca08ca 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -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; diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c index ce8bb12670..718357e290 100644 --- a/src/basic/parse-util.c +++ b/src/basic/parse-util.c @@ -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; - - d = makedev(x, y); - if ((unsigned) major(d) != x || (unsigned) minor(d) != y) + if (s[n] != ':') return -EINVAL; - *ret = d; + major = strndupa(s, n); + r = safe_atou(major, &x); + if (r < 0) + return r; + + 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; } diff --git a/src/basic/path-util.c b/src/basic/path-util.c index b7f91ee3ae..7d1e0f3f2d 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -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) { diff --git a/src/basic/path-util.h b/src/basic/path-util.h index 53b980d3c1..8bda450ff3 100644 --- a/src/basic/path-util.h +++ b/src/basic/path-util.h @@ -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) { diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 8b63eb360b..57700e2388 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -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; +} diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index 84400a6083..0a08e642b5 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -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); diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index a5e4de00b0..ceba7d0ff8 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -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; - - assert(r); - - k = get_ctty_devnr(pid, &devnr); - if (k < 0) - return k; - - sprintf(fn, "/dev/char/%u:%u", major(devnr), minor(devnr)); + int r; - k = readlink_malloc(fn, &s); - if (k < 0) { + r = get_ctty_devnr(pid, &devnr); + if (r < 0) + return r; - if (k != -ENOENT) - 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; - /* 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; + if (!b) { + const char *w; + + w = path_startswith(fn, "/dev/"); + if (w) { + b = strdup(w); + if (!b) + return -ENOMEM; + } else + b = TAKE_PTR(fn); } - *r = b; - if (_devnr) - *_devnr = devnr; + if (ret) + *ret = TAKE_PTR(b); + + if (ret_devnr) + *ret_devnr = devnr; return 0; } |