chiark / gitweb /
core: store the invocation ID in the per-service keyring
[elogind.git] / src / libelogind / sd-id128 / sd-id128.c
index 43cc86f43f5a25664892eb55e53d71ed43661448..14dfc31749d339525147abb5a431f72c5543e9e2 100644 (file)
@@ -129,6 +129,105 @@ _public_ int sd_id128_get_boot(sd_id128_t *ret) {
         return 0;
 }
 
         return 0;
 }
 
+static int get_invocation_from_keyring(sd_id128_t *ret) {
+
+        _cleanup_free_ char *description = NULL;
+        char *d, *p, *g, *u, *e;
+        unsigned long perms;
+        key_serial_t key;
+        size_t sz = 256;
+        uid_t uid;
+        gid_t gid;
+        int r, c;
+
+#define MAX_PERMS ((unsigned long) (KEY_POS_VIEW|KEY_POS_READ|KEY_POS_SEARCH| \
+                                    KEY_USR_VIEW|KEY_USR_READ|KEY_USR_SEARCH))
+
+        assert(ret);
+
+        key = request_key("user", "invocation_id", NULL, 0);
+        if (key == -1) {
+                /* Keyring support not available? No invocation key stored? */
+                if (IN_SET(errno, ENOSYS, ENOKEY))
+                        return 0;
+
+                return -errno;
+        }
+
+        for (;;) {
+                description = new(char, sz);
+                if (!description)
+                        return -ENOMEM;
+
+                c = keyctl(KEYCTL_DESCRIBE, key, (unsigned long) description, sz, 0);
+                if (c < 0)
+                        return -errno;
+
+                if ((size_t) c <= sz)
+                        break;
+
+                sz = c;
+                free(description);
+        }
+
+        /* The kernel returns a final NUL in the string, verify that. */
+        assert(description[c-1] == 0);
+
+        /* Chop off the final description string */
+        d = strrchr(description, ';');
+        if (!d)
+                return -EIO;
+        *d = 0;
+
+        /* Look for the permissions */
+        p = strrchr(description, ';');
+        if (!p)
+                return -EIO;
+
+        errno = 0;
+        perms = strtoul(p + 1, &e, 16);
+        if (errno > 0)
+                return -errno;
+        if (e == p + 1) /* Read at least one character */
+                return -EIO;
+        if (e != d) /* Must reached the end */
+                return -EIO;
+
+        if ((perms & ~MAX_PERMS) != 0)
+                return -EPERM;
+
+        *p = 0;
+
+        /* Look for the group ID */
+        g = strrchr(description, ';');
+        if (!g)
+                return -EIO;
+        r = parse_gid(g + 1, &gid);
+        if (r < 0)
+                return r;
+        if (gid != 0)
+                return -EPERM;
+        *g = 0;
+
+        /* Look for the user ID */
+        u = strrchr(description, ';');
+        if (!u)
+                return -EIO;
+        r = parse_uid(u + 1, &uid);
+        if (r < 0)
+                return r;
+        if (uid != 0)
+                return -EPERM;
+
+        c = keyctl(KEYCTL_READ, key, (unsigned long) ret, sizeof(sd_id128_t), 0);
+        if (c < 0)
+                return -errno;
+        if (c != sizeof(sd_id128_t))
+                return -EIO;
+
+        return 1;
+}
+
 _public_ int sd_id128_get_invocation(sd_id128_t *ret) {
         static thread_local sd_id128_t saved_invocation_id = {};
         int r;
 _public_ int sd_id128_get_invocation(sd_id128_t *ret) {
         static thread_local sd_id128_t saved_invocation_id = {};
         int r;
@@ -136,15 +235,31 @@ _public_ int sd_id128_get_invocation(sd_id128_t *ret) {
         assert_return(ret, -EINVAL);
 
         if (sd_id128_is_null(saved_invocation_id)) {
         assert_return(ret, -EINVAL);
 
         if (sd_id128_is_null(saved_invocation_id)) {
-                const char *e;
 
 
-                e = secure_getenv("INVOCATION_ID");
-                if (!e)
-                        return -ENXIO;
+                /* We first try to read the invocation ID from the kernel keyring. This has the benefit that it is not
+                 * fakeable by unprivileged code. If the information is not available in the keyring, we use
+                 * $INVOCATION_ID but ignore the data if our process was called by less privileged code
+                 * (i.e. secure_getenv() instead of getenv()).
+                 *
+                 * The kernel keyring is only relevant for system services (as for user services we don't store the
+                 * invocation ID in the keyring, as there'd be no trust benefit in that). The environment variable is
+                 * primarily relevant for user services, and sufficiently safe as no privilege boundary is involved. */
 
 
-                r = sd_id128_from_string(e, &saved_invocation_id);
+                r = get_invocation_from_keyring(&saved_invocation_id);
                 if (r < 0)
                         return r;
                 if (r < 0)
                         return r;
+
+                if (r == 0) {
+                        const char *e;
+
+                        e = secure_getenv("INVOCATION_ID");
+                        if (!e)
+                                return -ENXIO;
+
+                        r = sd_id128_from_string(e, &saved_invocation_id);
+                        if (r < 0)
+                                return r;
+                }
         }
 
         *ret = saved_invocation_id;
         }
 
         *ret = saved_invocation_id;