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;
}