summaryrefslogtreecommitdiff
path: root/src/basic
diff options
context:
space:
mode:
Diffstat (limited to 'src/basic')
-rw-r--r--src/basic/blockdev-util.c22
-rw-r--r--src/basic/capability-util.c125
-rw-r--r--src/basic/capability-util.h24
-rw-r--r--src/basic/fs-util.c50
-rw-r--r--src/basic/parse-util.c26
-rw-r--r--src/basic/path-util.c72
-rw-r--r--src/basic/path-util.h9
-rw-r--r--src/basic/stat-util.c98
-rw-r--r--src/basic/stat-util.h23
-rw-r--r--src/basic/terminal-util.c71
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;
}