Thread (24 messages) 24 messages, 3 authors, 2d ago

[RFC PATCH 1/6] keys: prevent quota overflow and widen quotalen and datalen to 32 bits

flat view
WARM2d SUPERSEDED

From: Jarkko Sakkinen <jarkko@kernel.org>
Date: 2026-10-16 21:53:49
Also in: keyrings, linux-cifs, lkml
Subsystem: common internet file system client (cifs and smb3), filesystems (vfs and infrastructure), keys/keyrings, security subsystem, the rest · Maintainers: Paulo Alcantara, Namjae Jeon, Alexander Viro, Christian Brauner, David Howells, Jarkko Sakkinen, Paul Moore, James Morris, "Serge E. Hallyn", Linus Torvalds

Revision rfc of 2 in this series.

Revisions (2)
  1. rfc current
  2. v2 [diff vs current]
In struct key, quotalen and datalen are 16-bit unsigned shorts, limiting
payload and quota tracking to 64 KiB. With large descriptions or payload
updates, addition and difference calculations can truncate or overflow,
corrupting user quota accounting.

Additionally, existing quota checks rely on undefined signed overflow
checks (e.g. user->qnbytes + quotalen < user->qnbytes) and do not guard
against user->qnkeys or user->qnbytes reaching INT_MAX.

Widen quotalen and datalen to unsigned 32-bit integers in struct key.
Validate description lengths against INT_MAX in key_alloc(), and check
quota headroom using safe subtraction across key_alloc(),
key_payload_reserve(), and keyctl_chown_key().

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Assisted-by: LLM
Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
---
 fs/smb/client/cifsacl.c |  6 +++---
 include/linux/key.h     |  4 ++--
 security/keys/key.c     | 25 ++++++++++++++++++++-----
 security/keys/keyctl.c  |  9 +++++----
 4 files changed, 30 insertions(+), 14 deletions(-)
diff --git a/fs/smb/client/cifsacl.c b/fs/smb/client/cifsacl.c
index c5e47a835f99..de2acc7234be 100644
--- a/fs/smb/client/cifsacl.c
+++ b/fs/smb/client/cifsacl.c
@@ -385,7 +385,7 @@ id_to_sid(unsigned int cid, uint sidtype, struct smb_sid *ssid)
 		goto out_revert_creds;
 	} else if (sidkey->datalen < CIFS_SID_BASE_SIZE) {
 		rc = smb_EIO1(smb_eio_trace_malformed_sid_key, sidkey->datalen);
-		cifs_dbg(FYI, "%s: Downcall contained malformed key (datalen=%hu)\n",
+		cifs_dbg(FYI, "%s: Downcall contained malformed key (datalen=%u)\n",
 			 __func__, sidkey->datalen);
 		goto invalidate_key;
 	}
@@ -403,7 +403,7 @@ id_to_sid(unsigned int cid, uint sidtype, struct smb_sid *ssid)
 	if (ksid_size > sidkey->datalen) {
 		rc = smb_EIO2(smb_eio_trace_malformed_ksid_key,
 			      ksid_size, sidkey->datalen);
-		cifs_dbg(FYI, "%s: Downcall contained malformed key (datalen=%hu, ksid_size=%u)\n",
+		cifs_dbg(FYI, "%s: Downcall contained malformed key (datalen=%u, ksid_size=%u)\n",
 			 __func__, sidkey->datalen, ksid_size);
 		goto invalidate_key;
 	}
@@ -498,7 +498,7 @@ sid_to_id(struct cifs_sb_info *cifs_sb, struct smb_sid *psid,
 	 */
 	BUILD_BUG_ON(sizeof(uid_t) != sizeof(gid_t));
 	if (sidkey->datalen != sizeof(uid_t)) {
-		cifs_dbg(FYI, "%s: Downcall contained malformed key (datalen=%hu)\n",
+		cifs_dbg(FYI, "%s: Downcall contained malformed key (datalen=%u)\n",
 			 __func__, sidkey->datalen);
 		key_invalidate(sidkey);
 		goto out_key_put;
diff --git a/include/linux/key.h b/include/linux/key.h
index e40b5267658f..26139c3adf62 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -216,8 +216,8 @@ struct key {
 	kuid_t			uid;
 	kgid_t			gid;
 	key_perm_t		perm;		/* access permissions */
-	unsigned short		quotalen;	/* length added to quota */
-	unsigned short		datalen;	/* payload data length
+	unsigned int		quotalen;	/* length added to quota */
+	unsigned int		datalen;	/* payload data length
 						 * - may not match RCU dereferenced payload
 						 * - payload should contain own length
 						 */
diff --git a/security/keys/key.c b/security/keys/key.c
index 6ebb8691551f..144745684334 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -245,6 +245,10 @@ struct key *key_alloc(struct key_type *type, const char *desc,
 	}
 
 	desclen = strlen(desc);
+	if (desclen >= INT_MAX || type->def_datalen > INT_MAX - desclen - 1) {
+		key = ERR_PTR(-EINVAL);
+		goto error;
+	}
 	quotalen = desclen + 1 + type->def_datalen;
 
 	/* get hold of the key tracking for this user */
@@ -261,10 +265,14 @@ struct key *key_alloc(struct key_type *type, const char *desc,
 			key_quota_root_maxbytes : key_quota_maxbytes;
 
 		spin_lock_irqsave(&user->lock, irqflags);
+		if (user->qnkeys == INT_MAX ||
+		    quotalen > INT_MAX - user->qnbytes)
+			goto no_quota;
+
 		if (!(flags & KEY_ALLOC_QUOTA_OVERRUN)) {
 			if (user->qnkeys + 1 > maxkeys ||
-			    user->qnbytes + quotalen > maxbytes ||
-			    user->qnbytes + quotalen < user->qnbytes)
+			    quotalen > maxbytes ||
+			    user->qnbytes > maxbytes - quotalen)
 				goto no_quota;
 		}
 
@@ -373,11 +381,16 @@ EXPORT_SYMBOL(key_alloc);
  */
 int key_payload_reserve(struct key *key, size_t datalen)
 {
-	int delta = (int)datalen - key->datalen;
+	int delta;
 	int ret = 0;
 
 	key_check(key);
 
+	if (datalen > INT_MAX)
+		return -EINVAL;
+
+	delta = (int)datalen - (int)key->datalen;
+
 	/* contemplate the quota adjustment */
 	if (delta != 0 && test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) {
 		unsigned maxbytes = uid_eq(key->user->uid, GLOBAL_ROOT_UID) ?
@@ -387,8 +400,10 @@ int key_payload_reserve(struct key *key, size_t datalen)
 		spin_lock_irqsave(&key->user->lock, flags);
 
 		if (delta > 0 &&
-		    (key->user->qnbytes + delta > maxbytes ||
-		     key->user->qnbytes + delta < key->user->qnbytes)) {
+		    (delta > INT_MAX - key->user->qnbytes ||
+		     delta > INT_MAX - key->quotalen ||
+		     delta > maxbytes ||
+		     key->user->qnbytes > maxbytes - delta)) {
 			ret = -EDQUOT;
 		}
 		else {
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 5a5efa26ed0e..6b528faab1a7 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -1014,10 +1014,11 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
 				key_quota_root_maxbytes : key_quota_maxbytes;
 
 			spin_lock_irqsave(&newowner->lock, flags);
-			if (newowner->qnkeys + 1 > maxkeys ||
-			    newowner->qnbytes + key->quotalen > maxbytes ||
-			    newowner->qnbytes + key->quotalen <
-			    newowner->qnbytes)
+			if (newowner->qnkeys == INT_MAX ||
+			    newowner->qnkeys + 1 > maxkeys ||
+			    key->quotalen > INT_MAX - newowner->qnbytes ||
+			    key->quotalen > maxbytes ||
+			    newowner->qnbytes > maxbytes - key->quotalen)
 				goto quota_overrun;
 
 			newowner->qnkeys++;
-- 
2.47.3
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help