Allow the ACL attached to a key to grant permissions to the denizens of a container object when request_key() is called. This allows separate permissions to those granted in the possessor set. int cfd = container_create("foo", 0); int ret = keyctl_grant_permission(key, KEY_ACE_SUBJ_CONTAINER, cfd, KEY_ACE_SEARCH); To allow request_key() to find a key, KEY_ACE_SEARCH must be included in the ACE. This will allow filesystems and network protocols (eg. AFS and AF_RXRPC) to use the key. For the request_key() system call to be able to find a key for a process inside the container, KEY_ACE_LINK must be granted also. Keys on the container keyring (and the container keyring itself) can be accessed directly by ID from inside the container if other KEY_ACE_* permits are granted. Signed-off-by: David Howells <dhowells@xxxxxxxxxx> --- include/linux/container.h | 6 ++- include/linux/key.h | 3 + include/uapi/linux/keyctl.h | 1 kernel/container.c | 41 ++++++++++++++++++- samples/vfs/test-container.c | 60 ++++++++++++++++++++++++++++ security/keys/permission.c | 90 ++++++++++++++++++++++++++++++++++++++---- security/keys/process_keys.c | 2 - 7 files changed, 188 insertions(+), 15 deletions(-) diff --git a/include/linux/container.h b/include/linux/container.h index 7424f7fb5560..cd82074c26a3 100644 --- a/include/linux/container.h +++ b/include/linux/container.h @@ -33,7 +33,11 @@ struct container { refcount_t usage; int exit_code; /* The exit code of 'init' */ const struct cred *cred; /* Creds for this container, including userns */ +#ifdef CONFIG_KEYS struct key *keyring; /* Externally managed container keyring */ + struct key_tag *tag; /* Container ID for key ACL */ + struct list_head req_key_traps; /* Traps for request-key upcalls */ +#endif struct nsproxy *ns; /* This container's namespaces */ struct path root; /* The root of the container's fs namespace */ struct task_struct *init; /* The 'init' task for this container */ @@ -43,7 +47,6 @@ struct container { struct list_head members; /* Member processes, guarded with ->lock */ struct list_head child_link; /* Link in parent->children */ struct list_head children; /* Child containers */ - struct list_head req_key_traps; /* Traps for request-key upcalls */ wait_queue_head_t waitq; /* Someone waiting for init to exit waits here */ unsigned long flags; #define CONTAINER_FLAG_INIT_STARTED 0 /* Init is started - certain ops now prohibited */ @@ -63,6 +66,7 @@ extern int copy_container(unsigned long flags, struct task_struct *tsk, extern void exit_container(struct task_struct *tsk); extern void put_container(struct container *c); extern long key_del_intercept(struct container *c, const char *type); +extern struct container *fd_to_container(int fd); static inline struct container *get_container(struct container *c) { diff --git a/include/linux/key.h b/include/linux/key.h index a38b89bd414c..01bccaa40047 100644 --- a/include/linux/key.h +++ b/include/linux/key.h @@ -90,6 +90,9 @@ struct key_ace { kuid_t uid; kgid_t gid; unsigned int subject_id; +#ifdef CONFIG_CONTAINERS + struct key_tag __rcu *container_tag; +#endif }; }; diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h index 045dcbb6bb8d..7136d14dd4d7 100644 --- a/include/uapi/linux/keyctl.h +++ b/include/uapi/linux/keyctl.h @@ -20,6 +20,7 @@ */ enum key_ace_subject_type { KEY_ACE_SUBJ_STANDARD = 0, /* subject is one of key_ace_standard_subject */ + KEY_ACE_SUBJ_CONTAINER = 1, /* subject is a container fd */ nr__key_ace_subject_type }; diff --git a/kernel/container.c b/kernel/container.c index f2706a45f364..81be4ed915c2 100644 --- a/kernel/container.c +++ b/kernel/container.c @@ -35,7 +35,9 @@ struct container init_container = { .members.next = &init_task.container_link, .members.prev = &init_task.container_link, .children = LIST_HEAD_INIT(init_container.children), +#ifdef CONFIG_KEYS .req_key_traps = LIST_HEAD_INIT(init_container.req_key_traps), +#endif .flags = (1 << CONTAINER_FLAG_INIT_STARTED), .lock = __SPIN_LOCK_UNLOCKED(init_container.lock), .seq = SEQCNT_ZERO(init_fs.seq), @@ -54,8 +56,6 @@ void put_container(struct container *c) while (c && refcount_dec_and_test(&c->usage)) { BUG_ON(!list_empty(&c->members)); - if (!list_empty(&c->req_key_traps)) - key_del_intercept(c, NULL); if (c->pid_ns) put_pid_ns(c->pid_ns); if (c->ns) @@ -71,7 +71,15 @@ void put_container(struct container *c) if (c->cred) put_cred(c->cred); +#ifdef CONFIG_KEYS + if (!list_empty(&c->req_key_traps)) + key_del_intercept(c, NULL); + if (c->tag) { + c->tag->removed = true; + key_put_tag(c->tag); + } key_put(c->keyring); +#endif security_container_free(c); kfree(c); c = parent; @@ -209,6 +217,24 @@ const struct file_operations container_fops = { .release = container_release, }; +/** + * fd_to_container - Get the container attached to an fd. + */ +struct container *fd_to_container(int fd) +{ + struct container *c = ERR_PTR(-EINVAL); + struct fd f = fdget(fd); + + if (!f.file) + return ERR_PTR(-EBADF); + + if (is_container_file(f.file)) + c = get_container(f.file->private_data); + + fdput(f); + return c; +} + /* * Handle fork/clone. * @@ -290,7 +316,9 @@ static struct container *alloc_container(const char __user *name) INIT_LIST_HEAD(&c->members); INIT_LIST_HEAD(&c->children); +#ifdef CONFIG_KEYS INIT_LIST_HEAD(&c->req_key_traps); +#endif init_waitqueue_head(&c->waitq); spin_lock_init(&c->lock); refcount_set(&c->usage, 1); @@ -305,8 +333,15 @@ static struct container *alloc_container(const char __user *name) ret = -EINVAL; if (strchr(c->name, '/')) goto err; - c->name[len] = 0; + +#ifdef CONFIG_KEYS + ret = -ENOMEM; + c->tag = kzalloc(sizeof(*c->tag), GFP_KERNEL); + if (!c->tag) + goto err; + refcount_set(&c->tag->usage, 1); +#endif return c; err: diff --git a/samples/vfs/test-container.c b/samples/vfs/test-container.c index e24048fdbe33..7b2081693fce 100644 --- a/samples/vfs/test-container.c +++ b/samples/vfs/test-container.c @@ -22,6 +22,30 @@ #define KEYCTL_CONTAINER_INTERCEPT 31 /* Intercept upcalls inside a container */ #define KEYCTL_SET_CONTAINER_KEYRING 35 /* Attach a keyring to a container */ +#define KEYCTL_GRANT_PERMISSION 36 /* Grant a permit to a key */ + +enum key_ace_subject_type { + KEY_ACE_SUBJ_STANDARD = 0, /* subject is one of key_ace_standard_subject */ + KEY_ACE_SUBJ_CONTAINER = 1, /* subject is a container fd */ +}; + +enum key_ace_standard_subject { + KEY_ACE_EVERYONE = 0, /* Everyone, including owner and group */ + KEY_ACE_GROUP = 1, /* The key's group */ + KEY_ACE_OWNER = 2, /* The owner of the key */ + KEY_ACE_POSSESSOR = 3, /* Any process that possesses of the key */ +}; + +#define KEY_ACE_VIEW 0x00000001 /* Can describe the key */ +#define KEY_ACE_READ 0x00000002 /* Can read the key content */ +#define KEY_ACE_WRITE 0x00000004 /* Can update/modify the key content */ +#define KEY_ACE_SEARCH 0x00000008 /* Can find the key by search */ +#define KEY_ACE_LINK 0x00000010 /* Can make a link to the key */ +#define KEY_ACE_SET_SECURITY 0x00000020 /* Can set owner, group, ACL */ +#define KEY_ACE_INVAL 0x00000040 /* Can invalidate the key */ +#define KEY_ACE_REVOKE 0x00000080 /* Can revoke the key */ +#define KEY_ACE_JOIN 0x00000100 /* Can join keyring */ +#define KEY_ACE_CLEAR 0x00000200 /* Can clear keyring */ /* Hope -1 isn't a syscall */ #ifndef __NR_fsopen @@ -190,7 +214,7 @@ void container_init(void) */ int main(int argc, char *argv[]) { - key_serial_t keyring; + key_serial_t keyring, key; pid_t pid; int fsfd, mfd, cfd, ws; @@ -271,11 +295,45 @@ int main(int argc, char *argv[]) exit(1); } + /* We need to grant the container permission to search for keys in the + * container keyring. + */ + if (keyctl(KEYCTL_GRANT_PERMISSION, keyring, KEY_ACE_SUBJ_CONTAINER, cfd, + KEY_ACE_SEARCH) < 0) { + perror("keyctl_grant/s"); + exit(1); + } + + if (keyctl(KEYCTL_GRANT_PERMISSION, keyring, + KEY_ACE_SUBJ_STANDARD, KEY_ACE_OWNER, 0) < 0) { + perror("keyctl_grant/s"); + exit(1); + } + if (keyctl(KEYCTL_SET_CONTAINER_KEYRING, cfd, keyring) < 0) { perror("keyctl_set_container_keyring"); exit(1); } + /* Create a key that can be accessed from within the container */ + printf("Sample key...\n"); + key = add_key("user", "foobar", "wibble", 6, keyring); + if (key == -1) { + perror("add_key/s"); + exit(1); + } + + if (keyctl(KEYCTL_GRANT_PERMISSION, key, KEY_ACE_SUBJ_CONTAINER, cfd, + KEY_ACE_VIEW | KEY_ACE_SEARCH | KEY_ACE_READ | KEY_ACE_LINK) < 0) { + perror("keyctl_grant/s"); + exit(1); + } + + if (keyctl_link(key, keyring) < 0) { + perror("keyctl_link"); + exit(1); + } + /* Create a keyring to catch upcalls. */ printf("Intercepting...\n"); keyring = add_key("keyring", "upcall", NULL, 0, KEY_SPEC_SESSION_KEYRING); diff --git a/security/keys/permission.c b/security/keys/permission.c index cb1359f6c668..f16d1665885f 100644 --- a/security/keys/permission.c +++ b/security/keys/permission.c @@ -13,6 +13,7 @@ #include <linux/security.h> #include <linux/user_namespace.h> #include <linux/uaccess.h> +#include <linux/container.h> #include "internal.h" struct key_acl default_key_acl = { @@ -130,6 +131,15 @@ int key_task_permission(const key_ref_t key_ref, const struct cred *cred, break; } break; +#ifdef CONFIG_CONTAINERS + case KEY_ACE_SUBJ_CONTAINER: { + const struct key_tag *tag = rcu_dereference(ace->container_tag); + + if (!tag->removed && current->container->tag == tag) + allow |= ace->perm; + break; + } +#endif } } @@ -185,8 +195,7 @@ EXPORT_SYMBOL(key_validate); */ unsigned int key_acl_to_perm(const struct key_acl *acl) { - unsigned int perm = 0, tperm; - int i; + unsigned int perm = 0, tperm, i; BUILD_BUG_ON(KEY_OTH_VIEW != KEY_ACE_VIEW || KEY_OTH_READ != KEY_ACE_READ || @@ -237,13 +246,37 @@ unsigned int key_acl_to_perm(const struct key_acl *acl) return perm; } +/* + * Clean up an ACL. + */ +static void key_free_acl(struct rcu_head *rcu) +{ + struct key_acl *acl = container_of(rcu, struct key_acl, rcu); +#ifdef CONFIG_CONTAINERS + struct key_tag *tag; + unsigned int i; + + for (i = 0; i < acl->nr_ace; i++) { + const struct key_ace *ace = &acl->aces[i]; + switch (ace->type) { + case KEY_ACE_SUBJ_CONTAINER: + tag = rcu_access_pointer(ace->container_tag); + key_put_tag(ace->container_tag); + break; + } + } +#endif + + kfree(acl); +} + /* * Destroy a key's ACL. */ void key_put_acl(struct key_acl *acl) { if (acl && refcount_dec_and_test(&acl->usage)) - kfree_rcu(acl, rcu); + call_rcu(&acl->rcu, key_free_acl); } /* @@ -297,6 +330,10 @@ static struct key_acl *key_alloc_acl(const struct key_acl *old_acl, int nr, int if (i == skip) continue; acl->aces[j] = old_acl->aces[i]; +#ifdef CONFIG_CONTAINERS + if (acl->aces[j].type == KEY_ACE_SUBJ_CONTAINER) + refcount_inc(&acl->aces[j].container_tag->usage); +#endif j++; } return acl; @@ -312,21 +349,39 @@ static long key_change_acl(struct key *key, struct key_ace *new_ace) old = rcu_dereference_protected(key->acl, lockdep_is_held(&key->sem)); - for (i = 0; i < old->nr_ace; i++) - if (old->aces[i].type == new_ace->type && - old->aces[i].subject_id == new_ace->subject_id) - goto found_match; + for (i = 0; i < old->nr_ace; i++) { + if (old->aces[i].type != new_ace->type) + continue; + switch (old->aces[i].type) { + case KEY_ACE_SUBJ_STANDARD: + if (old->aces[i].subject_id == new_ace->subject_id) + goto replace_ace; + break; +#ifdef CONFIG_CONTAINERS + case KEY_ACE_SUBJ_CONTAINER: + if (old->aces[i].container_tag == new_ace->container_tag) + goto replace_ace; + break; +#endif + default: + break; + } + } if (new_ace->perm == 0) - return 0; /* No permissions to remove. Add deny record? */ + return 0; /* No permissions to cancel. Add deny record? */ acl = key_alloc_acl(old, 1, -1); if (IS_ERR(acl)) return PTR_ERR(acl); acl->aces[i] = *new_ace; +#ifdef CONFIG_CONTAINERS + if (acl->aces[i].type == KEY_ACE_SUBJ_CONTAINER) + refcount_inc(&acl->aces[i].container_tag->usage); +#endif goto change; -found_match: +replace_ace: if (new_ace->perm == 0) goto delete_ace; if (new_ace->perm == old->aces[i].perm) @@ -360,6 +415,7 @@ long keyctl_grant_permission(key_serial_t keyid, key_ref_t key_ref; long ret; + memset(&new_ace, 0, sizeof(new_ace)); new_ace.type = type; new_ace.perm = perm; @@ -370,6 +426,18 @@ long keyctl_grant_permission(key_serial_t keyid, new_ace.subject_id = subject; break; +#ifdef CONFIG_CONTAINERS + case KEY_ACE_SUBJ_CONTAINER: { + struct container *c = fd_to_container(subject); + if (IS_ERR(c)) + return -EINVAL; + refcount_inc(&c->tag->usage); + new_ace.container_tag = c->tag; + put_container(c); + break; + } +#endif + default: return -ENOENT; } @@ -391,5 +459,9 @@ long keyctl_grant_permission(key_serial_t keyid, up_write(&key->sem); key_put(key); error: +#ifdef CONFIG_CONTAINERS + if (new_ace.type == KEY_ACE_SUBJ_CONTAINER && new_ace.container_tag) + key_put_tag(new_ace.container_tag); +#endif return ret; } diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c index 0a231ede4d2b..f296a1cc979a 100644 --- a/security/keys/process_keys.c +++ b/security/keys/process_keys.c @@ -466,7 +466,7 @@ key_ref_t search_my_process_keyrings(struct keyring_search_context *ctx) #ifdef CONFIG_CONTAINERS if (current->container->keyring) { key_ref = keyring_search_aux( - make_key_ref(current->container->keyring, 1), ctx); + make_key_ref(current->container->keyring, false), ctx); if (!IS_ERR(key_ref)) goto found;