summary refs log tree commit diff
path: root/security/keys
diff options
context:
space:
mode:
authorFredrik Tolf <fredrik@dolda2000.com>2006-06-26 00:24:51 -0700
committerLinus Torvalds <torvalds@g5.osdl.org>2006-06-26 09:58:18 -0700
commit5801649d8b83e7cb9b15839761bdee594653c294 (patch)
tree4534b606908596651a533b2f51418444b5a1e705 /security/keys
parent31204ed925b067d2bb65adb89501656f8274a32a (diff)
downloadlinux-5801649d8b83e7cb9b15839761bdee594653c294.tar.gz
[PATCH] keys: let keyctl_chown() change a key's owner
Let keyctl_chown() change a key's owner, including attempting to transfer the
quota burden to the new user.

Signed-off-by: David Howells <dhowells@redhat.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'security/keys')
-rw-r--r--security/keys/keyctl.c56
1 files changed, 48 insertions, 8 deletions
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index d74458522e98..329411cf8768 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -673,6 +673,7 @@ long keyctl_read_key(key_serial_t keyid, char __user *buffer, size_t buflen)
  */
 long keyctl_chown_key(key_serial_t id, uid_t uid, gid_t gid)
 {
+	struct key_user *newowner, *zapowner = NULL;
 	struct key *key;
 	key_ref_t key_ref;
 	long ret;
@@ -696,19 +697,50 @@ long keyctl_chown_key(key_serial_t id, uid_t uid, gid_t gid)
 	if (!capable(CAP_SYS_ADMIN)) {
 		/* only the sysadmin can chown a key to some other UID */
 		if (uid != (uid_t) -1 && key->uid != uid)
-			goto no_access;
+			goto error_put;
 
 		/* only the sysadmin can set the key's GID to a group other
 		 * than one of those that the current process subscribes to */
 		if (gid != (gid_t) -1 && gid != key->gid && !in_group_p(gid))
-			goto no_access;
+			goto error_put;
 	}
 
-	/* change the UID (have to update the quotas) */
+	/* change the UID */
 	if (uid != (uid_t) -1 && uid != key->uid) {
-		/* don't support UID changing yet */
-		ret = -EOPNOTSUPP;
-		goto no_access;
+		ret = -ENOMEM;
+		newowner = key_user_lookup(uid);
+		if (!newowner)
+			goto error_put;
+
+		/* transfer the quota burden to the new user */
+		if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) {
+			spin_lock(&newowner->lock);
+			if (newowner->qnkeys + 1 >= KEYQUOTA_MAX_KEYS ||
+			    newowner->qnbytes + key->quotalen >=
+			    KEYQUOTA_MAX_BYTES)
+				goto quota_overrun;
+
+			newowner->qnkeys++;
+			newowner->qnbytes += key->quotalen;
+			spin_unlock(&newowner->lock);
+
+			spin_lock(&key->user->lock);
+			key->user->qnkeys--;
+			key->user->qnbytes -= key->quotalen;
+			spin_unlock(&key->user->lock);
+		}
+
+		atomic_dec(&key->user->nkeys);
+		atomic_inc(&newowner->nkeys);
+
+		if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) {
+			atomic_dec(&key->user->nikeys);
+			atomic_inc(&newowner->nikeys);
+		}
+
+		zapowner = key->user;
+		key->user = newowner;
+		key->uid = uid;
 	}
 
 	/* change the GID */
@@ -717,12 +749,20 @@ long keyctl_chown_key(key_serial_t id, uid_t uid, gid_t gid)
 
 	ret = 0;
 
- no_access:
+error_put:
 	up_write(&key->sem);
 	key_put(key);
- error:
+	if (zapowner)
+		key_user_put(zapowner);
+error:
 	return ret;
 
+quota_overrun:
+	spin_unlock(&newowner->lock);
+	zapowner = newowner;
+	ret = -EDQUOT;
+	goto error_put;
+
 } /* end keyctl_chown_key() */
 
 /*****************************************************************************/