@@ -141,6 +141,12 @@ COMPAT_SYSCALL_DEFINE5(keyctl, u32, option,
return keyctl_restrict_keyring(arg2, compat_ptr(arg3),
compat_ptr(arg4));
+ case KEYCTL_GET_ACL:
+ return keyctl_get_acl(arg2, compat_ptr(arg3), arg4);
+
+ case KEYCTL_SET_ACL:
+ return keyctl_set_acl(arg2, compat_ptr(arg3), arg4);
+
default:
return -EOPNOTSUPP;
}
@@ -302,6 +302,14 @@ static inline long compat_keyctl_dh_compute(
#endif
#endif
+extern long keyctl_get_acl(key_serial_t keyid,
+ struct user_key_ace __user *_acl,
+ size_t nr_elem);
+extern long keyctl_set_acl(key_serial_t keyid,
+ const struct user_key_ace __user *_acl,
+ size_t nr_elem);
+
+
/*
* Debugging key validation
*/
@@ -961,6 +961,8 @@ long keyctl_setperm_key(key_serial_t id, unsigned int perm)
ace->mask |= KEY_ACE_REVOKE;
if (subset & KEY_OTH_SETATTR)
ace->mask |= KEY_ACE_SET_SECURITY;
+ if (subset & KEY_OTH_SEARCH)
+ ace->mask |= KEY_ACE_INVAL;
if (key->type == &key_type_keyring) {
ace->mask |= KEY_ACE_JOIN;
if (subset & KEY_OTH_WRITE)
@@ -1768,6 +1770,16 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
(const char __user *) arg3,
(const char __user *) arg4);
+ case KEYCTL_GET_ACL:
+ return keyctl_get_acl((key_serial_t)arg2,
+ (struct user_key_ace __user *)arg3,
+ (size_t)arg4);
+
+ case KEYCTL_SET_ACL:
+ return keyctl_set_acl((key_serial_t)arg2,
+ (const struct user_key_ace __user *)arg3,
+ (size_t)arg4);
+
default:
return -EOPNOTSUPP;
}
@@ -12,6 +12,7 @@
#include <linux/module.h>
#include <linux/security.h>
#include <linux/user_namespace.h>
+#include <linux/uaccess.h>
#include "internal.h"
struct key_acl default_key_acl = {
@@ -243,3 +244,184 @@ void key_put_acl(struct key_acl *acl)
if (refcount_dec_and_test(&acl->usage))
call_rcu(&acl->rcu, key_acl_rcu);
}
+
+/*
+ * Get the ACL attached to key.
+ */
+long keyctl_get_acl(key_serial_t keyid,
+ struct user_key_ace __user *_acl,
+ size_t max_acl_size)
+{
+ struct user_namespace *ns;
+ struct key_acl *acl;
+ struct key *key, *instkey;
+ key_ref_t key_ref;
+ long ret;
+ int i;
+
+ key_ref = lookup_user_key(keyid, KEY_LOOKUP_PARTIAL, KEY_NEED_VIEW);
+ if (IS_ERR(key_ref)) {
+ if (PTR_ERR(key_ref) != -EACCES)
+ return PTR_ERR(key_ref);
+
+ /* viewing a key under construction is also permitted if we
+ * have the authorisation token handy */
+ instkey = key_get_instantiation_authkey(keyid);
+ if (IS_ERR(instkey))
+ return PTR_ERR(instkey);
+ key_put(instkey);
+
+ key_ref = lookup_user_key(keyid, KEY_LOOKUP_PARTIAL, 0);
+ if (IS_ERR(key_ref))
+ return PTR_ERR(key_ref);
+ }
+
+ key = key_ref_to_ptr(key_ref);
+
+ down_read(&key->sem);
+ acl = key->acl;
+ refcount_inc(&acl->usage);
+ up_read(&key->sem);
+
+ if (acl->nr_ace * sizeof(struct user_key_ace) > max_acl_size)
+ goto out;
+
+ ns = current_user_ns();
+ ret = -EFAULT;
+ for (i = 0; i < acl->nr_ace; i++) {
+ const struct key_ace *ace = &acl->aces[i];
+
+ if (put_user(ace->mask, &_acl[i].mask) < 0)
+ goto error;
+
+ switch (ace->mask & KEY_ACE__IDENTITY) {
+ case KEY_ACE_SPECIAL:
+ if (put_user(ace->special_id, &_acl[i].special_id) < 0)
+ goto error;
+ break;
+ case KEY_ACE_UID:
+ if (put_user(from_kuid_munged(ns, ace->uid), &_acl[i].uid) < 0)
+ goto error;
+ break;
+ case KEY_ACE_GID:
+ if (put_user(from_kgid_munged(ns, ace->gid), &_acl[i].gid) < 0)
+ goto error;
+ break;
+ }
+ }
+
+out:
+ ret = acl->nr_ace * sizeof(struct user_key_ace);
+error:
+ return ret;
+}
+
+/*
+ * Get ACL from userspace.
+ */
+static struct key_acl *key_get_acl_from_user(const struct user_key_ace __user *_acl,
+ size_t acl_size)
+{
+ struct user_namespace *ns;
+ struct key_acl *acl;
+ long ret;
+ int nr_ace, i;
+
+ if (acl_size % sizeof(struct user_key_ace) != 0)
+ return ERR_PTR(-EINVAL);
+ nr_ace = acl_size / sizeof(struct user_key_ace);
+ if (nr_ace > 16)
+ return ERR_PTR(-EINVAL);
+
+ acl = kzalloc(sizeof(struct key_acl) + sizeof(struct key_ace) * nr_ace,
+ GFP_KERNEL);
+ if (!acl)
+ return ERR_PTR(-ENOMEM);
+
+ refcount_set(&acl->usage, 1);
+ acl->nr_ace = nr_ace;
+ ns = current_user_ns();
+ for (i = 0; i < nr_ace; i++) {
+ struct key_ace *ace = &acl->aces[i];
+ uid_t uid;
+ gid_t gid;
+
+ if (get_user(ace->mask, &_acl[i].mask) < 0)
+ goto fault;
+
+ switch (ace->mask & KEY_ACE__IDENTITY) {
+ case KEY_ACE_SPECIAL:
+ if (get_user(ace->special_id, &_acl[i].special_id) < 0)
+ goto fault;
+ if (ace->special_id == 0 ||
+ ace->special_id > KEY_ACE_NET_ADMIN)
+ goto inval;
+ break;
+ case KEY_ACE_UID:
+ if (get_user(uid, &_acl[i].uid) < 0)
+ goto fault;
+ ace->uid = make_kuid(ns, uid);
+ break;
+ case KEY_ACE_GID:
+ if (get_user(gid, &_acl[i].gid) < 0)
+ goto fault;
+ ace->gid = make_kgid(ns, gid);
+ break;
+ default:
+ goto inval;
+ }
+ }
+
+ return acl;
+
+fault:
+ ret = -EFAULT;
+ goto error;
+inval:
+ ret = -EINVAL;
+error:
+ key_put_acl(acl);
+ return ERR_PTR(ret);
+}
+
+/*
+ * Attach a new ACL to a key.
+ */
+long keyctl_set_acl(key_serial_t keyid,
+ const struct user_key_ace __user *_acl,
+ size_t acl_size)
+{
+ struct key_acl *acl, *discard;
+ struct key *key;
+ key_ref_t key_ref;
+ long ret;
+
+ acl = key_get_acl_from_user(_acl, acl_size);
+ if (IS_ERR(acl))
+ return PTR_ERR(acl);
+ discard = acl;
+
+ key_ref = lookup_user_key(keyid, KEY_LOOKUP_PARTIAL, KEY_NEED_SETSEC);
+ if (IS_ERR(key_ref)) {
+ ret = PTR_ERR(key_ref);
+ goto error_acl;
+ }
+
+ key = key_ref_to_ptr(key_ref);
+
+ ret = -EACCES;
+ down_write(&key->sem);
+
+ if (capable(CAP_SYS_ADMIN) || uid_eq(key->uid, current_fsuid())) {
+ discard = rcu_dereference_protected(key->acl,
+ lockdep_is_held(&key->sem));
+ rcu_assign_pointer(key->acl, acl);
+ ret = 0;
+ }
+
+ up_write(&key->sem);
+ key_put(key);
+error_acl:
+ key_put_acl(discard);
+ return ret;
+}