From e15decb318797f1d471588dc669c3e3b26f1b8b3 Mon Sep 17 00:00:00 2001
From: Andrew Deason <adeason@sinenomine.net>
Date: Mon, 18 Sep 2023 16:13:57 -0500
Subject: [PATCH 1/4] OPENAFS-SA-2024-002: viced: Refuse ACLs without '\0' in
 SRXAFS_StoreACL

CVE-2024-10396

Currently, the fileserver treats the ACL given in RXAFS_StoreACL as a
string, even though it is technically an AFSOpaque and could be not
NUL-terminated.

We give the ACL opaque/string to acl_Internalize_pr() to parse, which
will run off the end of the allocated buffer if the given ACL does not
contain a '\0' character. Usually this will result in a parse error
since we'll encounter garbage, but if the partially-garbage ACL
happens to parse successfully, some uninitialized data could make it
into the stored ACL.

In addition, if the given ACL is an opaque of length 0, we'll still
give the opaque pointer to acl_Internalize_pr(). In this case, the
pointer will point to &memZero, which happens to contain a NUL byte,
and so is treated like an empty string (which is not a valid ACL). But
the fact that this causes no problems is somewhat a coincidence, and
so should also be avoided.

To avoid both of these situations, just check if the given ACL string
contains a NUL byte. If it doesn't, or if it has length 0, refuse to
look at it and abort the call with EINVAL.

FIXES 135445

Change-Id: If55f72d6556bc7b1704a3848865bfb902ee9f92a
Reviewed-on: https://gerrit.openafs.org/15908
Reviewed-by: Benjamin Kaduk <kaduk@mit.edu>
Tested-by: Benjamin Kaduk <kaduk@mit.edu>
---
 src/viced/afsfileprocs.c | 35 +++++++++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/src/viced/afsfileprocs.c b/src/viced/afsfileprocs.c
index cfebe61673..631cd34eeb 100644
--- a/src/viced/afsfileprocs.c
+++ b/src/viced/afsfileprocs.c
@@ -3078,6 +3078,36 @@ printableACL(struct AFSOpaque *AccessList)
     return s;
 }
 
+/**
+ * Check if the given ACL blob is okay to use.
+ *
+ * If the given ACL is 0-length, or doesn't contain a NUL byte, return an error
+ * and refuse the process the given ACL. The ACL must contain a NUL byte,
+ * otherwise ACL-processing code may run off the end of the buffer and process
+ * uninitialized memory.
+ *
+ * If there is any data beyond the NUL byte, just ignore it and return success.
+ * We won't look at any post-NUL data, but theoretically clients could send
+ * such an ACL to us, since historically it's been allowed.
+ *
+ * @param[in] AccessList    The ACL blob to check
+ *
+ * @returns errno error code to abort the call with
+ * @retval 0 ACL is okay to use
+ */
+static afs_int32
+check_acl(struct AFSOpaque *AccessList)
+{
+    if (AccessList->AFSOpaque_len == 0) {
+	return EINVAL;
+    }
+    if (memchr(AccessList->AFSOpaque_val, '\0',
+	       AccessList->AFSOpaque_len) == NULL) {
+	return EINVAL;
+    }
+    return 0;
+}
+
 static afs_int32
 common_StoreACL(afs_uint64 opcode,
 		struct rx_call * acall, struct AFSFid * Fid,
@@ -3104,6 +3134,11 @@ common_StoreACL(afs_uint64 opcode,
     if ((errorCode = CallPreamble(acall, ACTIVECALL, Fid, &tcon, &thost)))
 	goto Bad_StoreACL;
 
+    errorCode = check_acl(AccessList);
+    if (errorCode != 0) {
+	goto Bad_StoreACL;
+    }
+
     /* Get ptr to client data for user Id for logging */
     t_client = (struct client *)rx_GetSpecific(tcon, rxcon_client_key);
     logHostAddr.s_addr = rxr_HostOf(tcon);
-- 
2.45.2


From f4dfc2d7183f126bc4a45b5cabc78c3de020925f Mon Sep 17 00:00:00 2001
From: Andrew Deason <adeason@sinenomine.net>
Date: Mon, 18 Sep 2023 16:14:07 -0500
Subject: [PATCH 2/4] OPENAFS-SA-2024-002: viced: Free ACL on
 acl_Internalize_pr error

CVE-2024-10396

Currently, we don't free 'newACL' if acl_Internalize_pr() fails. If
acl_Internalize_pr() has already allocated 'newACL', then the memory
associated with newACL will be leaked. This can happen if parsing the
given ACL fails at any point after successfully parsing the first
couple of lines in the ACL.

Change acl_FreeACL() to make freeing a NULL acl a no-op, to make it
easier to make sure the acl has been freed.

FIXES 135445

Change-Id: I87745fa9b6285574acdd5ecb613e80fa1ea37ae8
Reviewed-on: https://gerrit.openafs.org/15909
Reviewed-by: Benjamin Kaduk <kaduk@mit.edu>
Tested-by: Benjamin Kaduk <kaduk@mit.edu>
---
 src/libacl/aclprocs.c    |  4 ++++
 src/viced/afsfileprocs.c | 20 ++++++++++++++------
 2 files changed, 18 insertions(+), 6 deletions(-)

diff --git a/src/libacl/aclprocs.c b/src/libacl/aclprocs.c
index d4c3f8c4c9..cd07e813c9 100644
--- a/src/libacl/aclprocs.c
+++ b/src/libacl/aclprocs.c
@@ -116,6 +116,10 @@ acl_FreeACL(struct acl_accessList **acl)
     /* Releases the access list defined by acl.  Returns 0 always. */
     struct freeListEntry *x;
 
+    if (*acl == NULL) {
+	return 0;
+    }
+
     x = (struct freeListEntry *)
 	((char *)*acl - sizeof(struct freeListEntry *) - sizeof(int));
     *acl = NULL;
diff --git a/src/viced/afsfileprocs.c b/src/viced/afsfileprocs.c
index 631cd34eeb..3ac5042f09 100644
--- a/src/viced/afsfileprocs.c
+++ b/src/viced/afsfileprocs.c
@@ -1248,16 +1248,24 @@ RXFetch_AccessList(Vnode * targetptr, Vnode * parentwhentargetnotdir,
 static afs_int32
 RXStore_AccessList(Vnode * targetptr, struct AFSOpaque *AccessList)
 {
-    struct acl_accessList *newACL;	/* PlaceHolder for new access list */
+    int code;
+    struct acl_accessList *newACL = NULL;
 
     if (acl_Internalize_pr(hpr_NameToId, AccessList->AFSOpaque_val, &newACL)
-	!= 0)
-	return (EINVAL);
-    if ((newACL->size + 4) > VAclSize(targetptr))
-	return (E2BIG);
+	!= 0) {
+	code = EINVAL;
+	goto done;
+    }
+    if ((newACL->size + 4) > VAclSize(targetptr)) {
+	code = E2BIG;
+	goto done;
+    }
     memcpy((char *)VVnodeACL(targetptr), (char *)newACL, (int)(newACL->size));
+    code = 0;
+
+ done:
     acl_FreeACL(&newACL);
-    return (0);
+    return code;
 
 }				/*RXStore_AccessList */
 
-- 
2.45.2


From f701f704c7bc93cf5fd7cffaaa043cef6a99e77f Mon Sep 17 00:00:00 2001
From: Andrew Deason <adeason@sinenomine.net>
Date: Fri, 10 Jan 2020 12:01:50 -0600
Subject: [PATCH 3/4] OPENAFS-SA-2024-001: afs: Introduce afs_genpag()

CVE-2024-10394

Currently, several areas in the code call genpag() to generate a new
PAG id, but the signature of genpag() is very limited. To allow for
the code in genpag() to return errors and to examine the calling
user's credentials, introduce a new function, afs_genpag(), that does
the same thing as genpag(), but accepts creds and allows errors to be
returned.

Convert all existing callers to use afs_genpag() and to handle any
errors, though no errors are ever returned in this commit on its own.

To ensure there are no old callers of genpag() left around, change the
existing genpag() to be called genpagval(), and declare it static.

FIXES 135062

Change-Id: I5c96c0134db901f21ca30c8b3f57aeec1eb67aa5
Reviewed-on: https://gerrit.openafs.org/14090
Reviewed-by: Benjamin Kaduk <kaduk@mit.edu>
Tested-by: Benjamin Kaduk <kaduk@mit.edu>
---
 src/afs/AIX/osi_groups.c     | 10 +++++++-
 src/afs/DARWIN/osi_groups.c  | 10 +++++++-
 src/afs/FBSD/osi_groups.c    | 10 +++++++-
 src/afs/HPUX/osi_groups.c    | 10 +++++++-
 src/afs/IRIX/osi_groups.c    | 13 +++++++++-
 src/afs/LINUX/osi_groups.c   | 10 +++++++-
 src/afs/NBSD/osi_groups.c    |  9 ++++++-
 src/afs/OBSD/osi_groups.c    | 10 +++++++-
 src/afs/SOLARIS/osi_groups.c |  9 +++++--
 src/afs/UKERNEL/osi_groups.c |  9 ++++++-
 src/afs/afs_osi_pag.c        | 48 ++++++++++++++++++++++--------------
 src/afs/afs_pioctl.c         |  8 +++++-
 src/afs/afs_prototypes.h     |  2 +-
 src/afs/afs_stats.h          |  2 +-
 14 files changed, 128 insertions(+), 32 deletions(-)

diff --git a/src/afs/AIX/osi_groups.c b/src/afs/AIX/osi_groups.c
index 219dd6db7d..0a916a39f7 100644
--- a/src/afs/AIX/osi_groups.c
+++ b/src/afs/AIX/osi_groups.c
@@ -87,6 +87,14 @@ setpag(cred, pagvalue, newpag, change_parent)
     int j;
 
     AFS_STATCNT(setpag);
+
+    if (pagvalue == -1) {
+	code = afs_genpag(*cred, &pagvalue);
+	if (code != 0) {
+	    return (setuerror(code), code);
+	}
+    }
+
 #ifndef AFS_AIX51_ENV
     ngroups = afs_getgroups(*cred, NGROUPS, gidset);
     if (afs_get_pag_from_groups(gidset[0], gidset[1]) == NOPAG) {
@@ -100,7 +108,7 @@ setpag(cred, pagvalue, newpag, change_parent)
 	ngroups += 2;
     }
 #endif
-    *newpag = (pagvalue == -1 ? genpag() : pagvalue);
+    *newpag = pagvalue;
 #ifdef AFS_AIX51_ENV
     if (change_parent) {
 	code = kcred_setpag(*cred, PAG_AFS, *newpag);
diff --git a/src/afs/DARWIN/osi_groups.c b/src/afs/DARWIN/osi_groups.c
index 5899c7287e..ecb49d6df6 100644
--- a/src/afs/DARWIN/osi_groups.c
+++ b/src/afs/DARWIN/osi_groups.c
@@ -98,6 +98,14 @@ setpag(proc, cred, pagvalue, newpag, change_parent)
     int j;
 
     AFS_STATCNT(setpag);
+
+    if (pagvalue == -1) {
+	code = afs_genpag(*cred, &pagvalue);
+	if (code != 0) {
+	    return code;
+	}
+    }
+
     ngroups = afs_getgroups(*cred, NGROUPS, gidset);
     if (afs_get_pag_from_groups(gidset[1], gidset[2]) == NOPAG) {
 	/* We will have to shift grouplist to make room for pag */
@@ -109,7 +117,7 @@ setpag(proc, cred, pagvalue, newpag, change_parent)
 	}
 	ngroups += 2;
     }
-    *newpag = (pagvalue == -1 ? genpag() : pagvalue);
+    *newpag = pagvalue;
     afs_get_groups_from_pag(*newpag, &gidset[1], &gidset[2]);
     code = afs_setgroups(proc, cred, ngroups, gidset, change_parent);
     return code;
diff --git a/src/afs/FBSD/osi_groups.c b/src/afs/FBSD/osi_groups.c
index 2267550bf8..e288326c7d 100644
--- a/src/afs/FBSD/osi_groups.c
+++ b/src/afs/FBSD/osi_groups.c
@@ -80,6 +80,14 @@ setpag(struct thread *td, struct ucred **cred, afs_uint32 pagvalue,
     int j;
 
     AFS_STATCNT(setpag);
+
+    if (pagvalue == -1) {
+	code = afs_genpag(*cred, &pagvalue);
+	if (code != 0) {
+	    return code;
+	}
+    }
+
     gidset = osi_Alloc(gidset_len * sizeof(gid_t));
     ngroups = afs_getgroups(*cred, gidset_len, gidset);
     if (afs_get_pag_from_groups(gidset[1], gidset[2]) == NOPAG) {
@@ -92,7 +100,7 @@ setpag(struct thread *td, struct ucred **cred, afs_uint32 pagvalue,
 	}
 	ngroups += 2;
     }
-    *newpag = (pagvalue == -1 ? genpag() : pagvalue);
+    *newpag = pagvalue;
     afs_get_groups_from_pag(*newpag, &gidset[1], &gidset[2]);
     code = afs_setgroups(td, cred, ngroups, gidset, change_parent);
     osi_Free(gidset, gidset_len * sizeof(gid_t));
diff --git a/src/afs/HPUX/osi_groups.c b/src/afs/HPUX/osi_groups.c
index c3b024c23e..1b28a43f5d 100644
--- a/src/afs/HPUX/osi_groups.c
+++ b/src/afs/HPUX/osi_groups.c
@@ -71,6 +71,14 @@ setpag(cred, pagvalue, newpag, change_parent)
     int j;
 
     AFS_STATCNT(setpag);
+
+    if (pagvalue == -1) {
+	code = afs_genpag(*cred, &pagvalue);
+	if (code != 0) {
+	    return (setuerror(code), code);
+	}
+    }
+
     ngroups = afs_getgroups(*cred, NGROUPS, gidset);
     if (afs_get_pag_from_groups(gidset[0], gidset[1]) == NOPAG) {
 	/* We will have to shift grouplist to make room for pag */
@@ -82,7 +90,7 @@ setpag(cred, pagvalue, newpag, change_parent)
 	}
 	ngroups += 2;
     }
-    *newpag = (pagvalue == -1 ? genpag() : pagvalue);
+    *newpag = pagvalue;
     afs_get_groups_from_pag(*newpag, &gidset[0], &gidset[1]);
 
     if (code = afs_setgroups(cred, ngroups, gidset, change_parent)) {
diff --git a/src/afs/IRIX/osi_groups.c b/src/afs/IRIX/osi_groups.c
index d3f8b33474..9e1d5c0095 100644
--- a/src/afs/IRIX/osi_groups.c
+++ b/src/afs/IRIX/osi_groups.c
@@ -194,6 +194,17 @@ setpag(cred, pagvalue, newpag, change_parent)
 
     AFS_STATCNT(setpag);
 
+    if (pagvalue == -1) {
+	code = afs_genpag(*cred, &pagvalue);
+	if (code != 0) {
+#if defined(KERNEL_HAVE_UERROR)
+	    return (setuerror(code), code);
+#else
+	    return code;
+#endif
+	}
+    }
+
     ngroups = afs_getgroups(*cred, NGROUPS, gidset);
     if (afs_get_pag_from_groups(gidset[0], gidset[1]) == NOPAG) {
 	/* We will have to shift grouplist to make room for pag */
@@ -209,7 +220,7 @@ setpag(cred, pagvalue, newpag, change_parent)
 	}
 	ngroups += 2;
     }
-    *newpag = (pagvalue == -1 ? genpag() : pagvalue);
+    *newpag = pagvalue;
     afs_get_groups_from_pag(*newpag, &gidset[0], &gidset[1]);
     if (code = afs_setgroups(cred, ngroups, gidset, change_parent)) {
 #if defined(KERNEL_HAVE_UERROR)
diff --git a/src/afs/LINUX/osi_groups.c b/src/afs/LINUX/osi_groups.c
index aee5dd516e..a060633d5f 100644
--- a/src/afs/LINUX/osi_groups.c
+++ b/src/afs/LINUX/osi_groups.c
@@ -150,11 +150,19 @@ __setpag(cred_t **cr, afs_uint32 pagvalue, afs_uint32 *newpag,
 {
     struct group_info *group_info;
     struct group_info *tmp;
+    int code;
+
+    if (pagvalue == -1) {
+	code = afs_genpag(*cr, &pagvalue);
+	if (code != 0) {
+	    return code;
+	}
+    }
 
     get_group_info(afs_cr_group_info(*cr));
     group_info = afs_cr_group_info(*cr);
 
-    *newpag = (pagvalue == -1 ? genpag() : pagvalue);
+    *newpag = pagvalue;
     afs_linux_pag_to_groups(*newpag, group_info, &tmp);
 
     if (old_groups) {
diff --git a/src/afs/NBSD/osi_groups.c b/src/afs/NBSD/osi_groups.c
index 9ee543932b..81043f4ff3 100644
--- a/src/afs/NBSD/osi_groups.c
+++ b/src/afs/NBSD/osi_groups.c
@@ -84,6 +84,13 @@ setpag(afs_proc_t *proc, afs_ucred_t **cred, afs_uint32 pagvalue,
     int j;
 
     AFS_STATCNT(setpag);
+
+    if (pagvalue == -1) {
+	code = afs_genpag(*cred, &pagvalue);
+	if (code != 0) {
+	    return code;
+	}
+    }
     ngroups = osi_getgroups(*cred, NGROUPS, gidset);
     if (afs_get_pag_from_groups(gidset[1], gidset[2]) == NOPAG) {
 	/* We will have to shift grouplist to make room for pag */
@@ -95,7 +102,7 @@ setpag(afs_proc_t *proc, afs_ucred_t **cred, afs_uint32 pagvalue,
 	}
 	ngroups += 2;
     }
-    *newpag = (pagvalue == -1 ? genpag() : pagvalue);
+    *newpag = pagvalue;
     afs_get_groups_from_pag(*newpag, &gidset[1], &gidset[2]);
     code = osi_setgroups(proc, cred, ngroups, gidset, change_parent);
     return code;
diff --git a/src/afs/OBSD/osi_groups.c b/src/afs/OBSD/osi_groups.c
index 208e5a7447..98ccf0421d 100644
--- a/src/afs/OBSD/osi_groups.c
+++ b/src/afs/OBSD/osi_groups.c
@@ -81,6 +81,14 @@ setpag(struct proc *proc, struct ucred **cred, afs_uint32 pagvalue,
     int j;
 
     AFS_STATCNT(setpag);
+
+    if (pagvalue == -1) {
+	code = afs_genpag(*cred, &pagvalue);
+	if (code != 0) {
+	    return code;
+	}
+    }
+
     ngroups = afs_getgroups(*cred, NGROUPS, gidset);
     /*
      * If the group list is empty, use the task's primary group as the group
@@ -102,7 +110,7 @@ setpag(struct proc *proc, struct ucred **cred, afs_uint32 pagvalue,
 	}
 	ngroups += 2;
     }
-    *newpag = (pagvalue == -1 ? genpag() : pagvalue);
+    *newpag = pagvalue;
     afs_get_groups_from_pag(*newpag, &gidset[1], &gidset[2]);
     code = afs_setgroups(proc, cred, ngroups, gidset, change_parent);
     return code;
diff --git a/src/afs/SOLARIS/osi_groups.c b/src/afs/SOLARIS/osi_groups.c
index d6e7a993fe..cd8df917e5 100644
--- a/src/afs/SOLARIS/osi_groups.c
+++ b/src/afs/SOLARIS/osi_groups.c
@@ -168,6 +168,13 @@ setpag(cred, pagvalue, newpag, change_parent)
 
     AFS_STATCNT(setpag);
 
+    if (pagvalue == -1) {
+	code = afs_genpag(*cred, &pagvalue);
+	if (code != 0) {
+	    return code;
+	}
+    }
+
     /* Derive gidset size from running kernel's ngroups_max;
      * default 16, but configurable up to 32 (Sol10) or
      * 1024 (Sol11).
@@ -177,8 +184,6 @@ setpag(cred, pagvalue, newpag, change_parent)
     /* must use osi_Alloc, osi_AllocSmallSpace may not be enough. */
     gidset = osi_Alloc(gidset_sz);
 
-    pagvalue = (pagvalue == -1 ? genpag() : pagvalue);
-
     mutex_enter(&curproc->p_crlock);
     ngroups = afs_getgroups(*cred, gidset);
 
diff --git a/src/afs/UKERNEL/osi_groups.c b/src/afs/UKERNEL/osi_groups.c
index c053e150d3..b25d22d112 100644
--- a/src/afs/UKERNEL/osi_groups.c
+++ b/src/afs/UKERNEL/osi_groups.c
@@ -74,6 +74,13 @@ usr_setpag(struct usr_ucred **cred, afs_uint32 pagvalue, afs_uint32 * newpag,
 
     AFS_STATCNT(setpag);
 
+    if (pagvalue == -1) {
+	code = afs_genpag(*cred, &pagvalue);
+	if (code != 0) {
+	    return code;
+	}
+    }
+
     gidset = osi_AllocSmallSpace(AFS_SMALLOCSIZ);
     ngroups = afs_getgroups(*cred, gidset);
 
@@ -88,7 +95,7 @@ usr_setpag(struct usr_ucred **cred, afs_uint32 pagvalue, afs_uint32 * newpag,
 	}
 	ngroups += 2;
     }
-    *newpag = (pagvalue == -1 ? genpag() : pagvalue);
+    *newpag = pagvalue;
     afs_get_groups_from_pag(*newpag, &gidset[0], &gidset[1]);
     if ((code = afs_setgroups(cred, ngroups, gidset, change_parent))) {
 	osi_FreeSmallSpace((char *)gidset);
diff --git a/src/afs/afs_osi_pag.c b/src/afs/afs_osi_pag.c
index 162735b1fc..80b0cc3217 100644
--- a/src/afs/afs_osi_pag.c
+++ b/src/afs/afs_osi_pag.c
@@ -9,7 +9,7 @@
 
 /*
  * Implements:
- * genpag
+ * genpagval
  * getpag
  * afs_setpag
  * AddPag
@@ -67,8 +67,8 @@ afs_uint32 pagCounter = 0;
  * secure (although of course not absolutely secure).
 */
 #if !defined(UKERNEL)
-afs_uint32
-genpag(void)
+static afs_uint32
+genpagval(void)
 {
     AFS_STATCNT(genpag);
 #ifdef AFS_LINUX_ENV
@@ -96,8 +96,8 @@ getpag(void)
 /* Web enhancement: we don't need to restrict pags to 41XXXXXX since
  * we are not sharing the space with anyone.  So we use the full 32 bits. */
 
-afs_uint32
-genpag(void)
+static afs_uint32
+genpagval(void)
 {
     AFS_STATCNT(genpag);
 #ifdef AFS_LINUX_ENV
@@ -182,6 +182,13 @@ afs_pag_wait(afs_ucred_t *acred)
     return code;
 }
 
+afs_int32
+afs_genpag(afs_ucred_t *acred, afs_uint32 *apag)
+{
+    *apag = genpagval();
+    return 0;
+}
+
 int
 #if	defined(AFS_SUN5_ENV)
 afs_setpag(afs_ucred_t **credpp)
@@ -205,6 +212,7 @@ afs_setpag(void)
 #endif
 
     int code = 0;
+    afs_uint32 pag;
 
 #if defined(AFS_SGI_ENV) && defined(MP)
     /* This is our first chance to get the global lock. */
@@ -218,15 +226,19 @@ afs_setpag(void)
 	goto done;
     }
 
+    code = afs_genpag(acred, &pag);
+    if (code) {
+	goto done;
+    }
 
 #if	defined(AFS_SUN5_ENV)
-    code = AddPag(genpag(), credpp);
+    code = AddPag(pag, credpp);
 #elif	defined(AFS_FBSD_ENV)
-    code = AddPag(td, genpag(), &td->td_ucred);
+    code = AddPag(td, pag, &td->td_ucred);
 #elif   defined(AFS_NBSD40_ENV)
-    code = AddPag(p, genpag(), &p->l_proc->p_cred);
+    code = AddPag(p, pag, &p->l_proc->p_cred);
 #elif	defined(AFS_XBSD_ENV)
-    code = AddPag(p, genpag(), &p->p_rcred);
+    code = AddPag(p, pag, &p->p_rcred);
 #elif	defined(AFS_AIX41_ENV)
     {
 	struct ucred *credp;
@@ -234,7 +246,7 @@ afs_setpag(void)
 
 	credp = crref();
 	credp0 = credp;
-	code = AddPag(genpag(), &credp);
+	code = AddPag(pag, &credp);
 	/* If AddPag() didn't make a new cred, then free our cred ref */
 	if (credp == credp0) {
 	    crfree(credp);
@@ -243,36 +255,36 @@ afs_setpag(void)
 #elif	defined(AFS_HPUX110_ENV)
     {
 	struct ucred *credp = p_cred(u.u_procp);
-	code = AddPag(genpag(), &credp);
+	code = AddPag(pag, &credp);
     }
 #elif	defined(AFS_SGI_ENV)
     {
 	cred_t *credp;
 	credp = OSI_GET_CURRENT_CRED();
-	code = AddPag(genpag(), &credp);
+	code = AddPag(pag, &credp);
     }
 #elif	defined(AFS_LINUX_ENV)
     {
 	afs_ucred_t *credp = crref();
-	code = AddPag(genpag(), &credp);
+	code = AddPag(pag, &credp);
 	crfree(credp);
     }
 #elif defined(AFS_DARWIN80_ENV)
     {
 	afs_ucred_t *credp = kauth_cred_proc_ref(p);
-	code = AddPag(p, genpag(), &credp);
+	code = AddPag(p, pag, &credp);
 	crfree(credp);
     }
 #elif defined(AFS_DARWIN_ENV)
     {
 	afs_ucred_t *credp = crdup(p->p_cred->pc_ucred);
-	code = AddPag(p, genpag(), &credp);
+	code = AddPag(p, pag, &credp);
 	crfree(credp);
     }
 #elif defined(UKERNEL)
-    code = AddPag(genpag(), &(get_user_struct()->u_cred));
+    code = AddPag(pag, &(get_user_struct()->u_cred));
 #else
-    code = AddPag(genpag(), &u.u_cred);
+    code = AddPag(pag, &u.u_cred);
 #endif
 
   done:
@@ -294,7 +306,7 @@ afs_setpag(void)
 /*
  * afs_setpag_val
  * This function is like setpag but sets the current thread's pag id to a
- * caller-provided value instead of calling genpag().  This implements a
+ * caller-provided value instead of calling afs_genpag().  This implements a
  * form of token caching since the caller can recall a particular pag value
  * for the thread to restore tokens, rather than reauthenticating.
  */
diff --git a/src/afs/afs_pioctl.c b/src/afs/afs_pioctl.c
index ae239bac73..37b1ae1152 100644
--- a/src/afs/afs_pioctl.c
+++ b/src/afs/afs_pioctl.c
@@ -4721,7 +4721,13 @@ HandleClientContext(struct afs_ioctl *ablob, int *com,
     *acred = newcred;
     if (!code && *com == PSETPAG) {
 	/* Special case for 'setpag' */
-	afs_uint32 pagvalue = genpag();
+	afs_uint32 pagvalue;
+
+	code = afs_genpag(*acred, &pagvalue);
+	if (code != 0) {
+	    EXP_RELE(outexporter);
+	    return code;
+	}
 
 	au = afs_GetUser(pagvalue, -1, WRITE_LOCK); /* a new unixuser struct */
 	/*
diff --git a/src/afs/afs_prototypes.h b/src/afs/afs_prototypes.h
index 79ddb8996b..db69dc09a4 100644
--- a/src/afs/afs_prototypes.h
+++ b/src/afs/afs_prototypes.h
@@ -526,7 +526,7 @@ extern int afs_setpag(afs_proc_t *p, void *args, int *retval);
 extern int afs_setpag(void);
 #endif
 
-extern afs_uint32 genpag(void);
+extern afs_int32 afs_genpag(afs_ucred_t *acred, afs_uint32 *apag);
 extern afs_uint32 getpag(void);
 #if defined(AFS_FBSD_ENV)
 extern int AddPag(struct thread *td, afs_int32 aval, afs_ucred_t **credpp);
diff --git a/src/afs/afs_stats.h b/src/afs/afs_stats.h
index e19ad14e30..c888acfeab 100644
--- a/src/afs/afs_stats.h
+++ b/src/afs/afs_stats.h
@@ -545,7 +545,7 @@ struct afs_MeanStats {
     AFS_CS(afs_swapvp)		/* afs_vfsops.c */ \
     AFS_CS(afs_AddMarinerName)	/* afs_vnodeops.c */ \
     AFS_CS(afs_setpag)		/* afs_vnodeops.c */ \
-    AFS_CS(genpag)		/* afs_vnodeops.c */ \
+    AFS_CS(genpag)		/* afs_vnodeops.c, renamed to genpagval() */ \
     AFS_CS(getpag)		/* afs_vnodeops.c */ \
     AFS_CS(afs_GetMariner)	/* afs_vnodeops.c */ \
     AFS_CS(afs_badop)		/* afs_vnodeops.c */ \
-- 
2.45.2


From 0358648dbed7656e7bda30f6f0ea6e8e01bf6527 Mon Sep 17 00:00:00 2001
From: Andrew Deason <adeason@sinenomine.net>
Date: Fri, 10 Jan 2020 12:40:15 -0600
Subject: [PATCH 4/4] OPENAFS-SA-2024-001: afs: Throttle PAG creation in
 afs_genpag()

CVE-2024-10394

Currently, we only throttle PAG creation in afs_setpag(). But there
are several callers that call setpag() directly, not via afs_setpag;
notably _settok_setParentPag in afs_pioctl.c. When setpag() is called
with a PAG value of -1, it generates a new PAG internally without any
throttling. So, those callers effectively bypass the PAG throttling
mechanism, which allows a calling user to create PAGs without any
delay.

To avoid this, move our afs_pag_wait call from afs_setpag() to
afs_genpag(), which all code uses to generate a new PAG value. This
ensures that PAG creation is always throttled for unprivileged users.

FIXES 135062

Change-Id: Ic4cb352edaa693984995fbdb6dc35b89686e8470
Reviewed-on: https://gerrit.openafs.org/15907
Reviewed-by: Benjamin Kaduk <kaduk@mit.edu>
Tested-by: Benjamin Kaduk <kaduk@mit.edu>
---
 src/afs/afs_osi_pag.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/afs/afs_osi_pag.c b/src/afs/afs_osi_pag.c
index 80b0cc3217..ec0a0dd435 100644
--- a/src/afs/afs_osi_pag.c
+++ b/src/afs/afs_osi_pag.c
@@ -185,6 +185,11 @@ afs_pag_wait(afs_ucred_t *acred)
 afs_int32
 afs_genpag(afs_ucred_t *acred, afs_uint32 *apag)
 {
+    afs_int32 code;
+    code = afs_pag_wait(acred);
+    if (code) {
+	return code;
+    }
     *apag = genpagval();
     return 0;
 }
@@ -221,11 +226,6 @@ afs_setpag(void)
 
     AFS_STATCNT(afs_setpag);
 
-    code = afs_pag_wait(acred);
-    if (code) {
-	goto done;
-    }
-
     code = afs_genpag(acred, &pag);
     if (code) {
 	goto done;
-- 
2.45.2

