Version in base suite: 1.8.9-1 Base version: openafs_1.8.9-1 Target version: openafs_1.8.9-1+deb12u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/o/openafs/openafs_1.8.9-1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/o/openafs/openafs_1.8.9-1+deb12u1.dsc changelog | 13 patches/Properly-type-afs_osi_suser-cred-arg.patch | 275 +++ patches/openafs-sa-2024-001-stable18.patch | 492 ++++++ patches/openafs-sa-2024-002-stable18.patch | 1076 +++++++++++++ patches/openafs-sa-2024-003-stable18.patch | 1617 +++++++++++++++++++++ patches/series | 4 6 files changed, 3477 insertions(+) diff -Nru openafs-1.8.9/debian/changelog openafs-1.8.9/debian/changelog --- openafs-1.8.9/debian/changelog 2022-12-22 18:37:54.000000000 +0000 +++ openafs-1.8.9/debian/changelog 2024-12-25 20:19:02.000000000 +0000 @@ -1,3 +1,16 @@ +openafs (1.8.9-1+deb12u1) bookworm-security; urgency=high + + * Non-maintainer upload by the Security Team. + * afs: Properly type afs_osi_suser cred arg + * Theft of credentials in Unix client PAGs (CVE-2024-10394) + (Closes: #1087406, #1087407) + * Fileserver crash and possible information leak on StoreACL/FetchACL + (CVE-2024-10396) (Closes: #1087406, #1087407) + * Preallocated buffer overflows in XDR responses (CVE-2024-10397) + (Closes: #1087406, #1087407) + + -- Salvatore Bonaccorso Wed, 25 Dec 2024 21:19:02 +0100 + openafs (1.8.9-1) unstable; urgency=medium [ Benjamin Kaduk ] diff -Nru openafs-1.8.9/debian/patches/Properly-type-afs_osi_suser-cred-arg.patch openafs-1.8.9/debian/patches/Properly-type-afs_osi_suser-cred-arg.patch --- openafs-1.8.9/debian/patches/Properly-type-afs_osi_suser-cred-arg.patch 1970-01-01 00:00:00.000000000 +0000 +++ openafs-1.8.9/debian/patches/Properly-type-afs_osi_suser-cred-arg.patch 2024-12-25 20:19:02.000000000 +0000 @@ -0,0 +1,275 @@ +From: Andrew Deason +Date: Tue, 14 Jan 2020 10:51:42 -0600 +Subject: afs: Properly type afs_osi_suser cred arg +Origin: http://git.openafs.org/?p=openafs.git;a=commit;h=7c3c93db2977765a8b82426f0524b380f896b82f + +Currently, afs_osi_suser is declared with a void* argument, even +though its only argument is always effectively a afs_ucred_t*. This +allows us to call afs_osi_suser with any pointer type without the +compiler complaining. Currently, some callers call afs_osi_suser with +an incorrectly-typed afs_ucred_t** instead, like so: + + func(afs_ucred_t **credpp) + { + afs_ucred_t **acred = *acredpp; /* incorrect assignment */ + if (afs_osi_suser(acred)) { + /* ... */ + } + } + +The actual code in the tree hides this to some degree behind various +function calls and layers of indirection (e.g. afs_suser()), but this +is effectively what we do. This causes compiler warnings because we +are doing incorrect pointer assignments, but the end result works +because afs_osi_suser actually uses an afs_ucred_t*. + +The type confusion makes it very easy to accidentally give the wrong +type to afs_osi_suser. This only really matters on SOLARIS, since that +is the only platform that actually uses its argument to +afs_osi_suser(). + +To fix all of this, just declare afs_osi_suser as taking an +afs_ucred_t*, and fix all of the relevant functions to handle the +right type. + +Reviewed-on: https://gerrit.openafs.org/14085 +Tested-by: BuildBot +Reviewed-by: Benjamin Kaduk +(cherry picked from commit 4ce922d339777faf647f7129f5ae3f173a7870b1) + +Change-Id: I1a6ce7788e86c32e554f87785f96f1d7b56d1496 +Reviewed-on: https://gerrit.openafs.org/15308 +Reviewed-by: Andrew Deason +Tested-by: BuildBot +Reviewed-by: Mark Vitale +Reviewed-by: Michael Meffie +Reviewed-by: Stephan Wiesand +--- + src/afs/AIX/osi_misc.c | 2 +- + src/afs/DARWIN/osi_misc.c | 2 +- + src/afs/DARWIN/osi_prototypes.h | 2 +- + src/afs/HPUX/osi_misc.c | 2 +- + src/afs/NBSD/osi_misc.c | 2 +- + src/afs/OBSD/osi_misc.c | 2 +- + src/afs/UKERNEL/afs_usrops.c | 4 ++-- + src/afs/UKERNEL/osi_machdep.h | 2 +- + src/afs/afs_osi.c | 2 +- + src/afs/afs_osi_pag.c | 16 ++++++++-------- + src/afs/afs_pioctl.c | 4 ++-- + src/afs/afs_prototypes.h | 2 +- + 12 files changed, 21 insertions(+), 21 deletions(-) + +diff --git a/src/afs/AIX/osi_misc.c b/src/afs/AIX/osi_misc.c +index 77609a92f9cc..be4336a29cc5 100644 +--- a/src/afs/AIX/osi_misc.c ++++ b/src/afs/AIX/osi_misc.c +@@ -167,7 +167,7 @@ aix_gnode_rele(vp) + * Note that it must NOT set errno. + */ + +-afs_suser(void *credp) ++afs_suser(afs_ucred_t *credp) + { + int rc; + char err; +diff --git a/src/afs/DARWIN/osi_misc.c b/src/afs/DARWIN/osi_misc.c +index 22dcfb047ac3..b0e006f828de 100644 +--- a/src/afs/DARWIN/osi_misc.c ++++ b/src/afs/DARWIN/osi_misc.c +@@ -170,7 +170,7 @@ osi_lookupname(char *aname, enum uio_seg seg, int followlink, + * Note that it must NOT set errno. + */ + int +-afs_suser(void *credp) ++afs_suser(afs_ucred_t *credp) + { + int error; + struct proc *p = current_proc(); +diff --git a/src/afs/DARWIN/osi_prototypes.h b/src/afs/DARWIN/osi_prototypes.h +index c93cf5b441a8..956211232a1d 100644 +--- a/src/afs/DARWIN/osi_prototypes.h ++++ b/src/afs/DARWIN/osi_prototypes.h +@@ -20,7 +20,7 @@ extern int osi_lookupname(char *aname, enum uio_seg seg, int followlink, + struct vnode **vpp); + extern int osi_lookupname_user(user_addr_t aname, enum uio_seg seg, + int followlink, struct vnode **vpp); +-extern int afs_suser(void *credp); ++extern int afs_suser(afs_ucred_t *credp); + extern void get_vfs_context(void); + extern void put_vfs_context(void); + +diff --git a/src/afs/HPUX/osi_misc.c b/src/afs/HPUX/osi_misc.c +index c80bf6a78e26..ab2eec7c8785 100644 +--- a/src/afs/HPUX/osi_misc.c ++++ b/src/afs/HPUX/osi_misc.c +@@ -27,7 +27,7 @@ + * Here we have to save and restore errno since the HP-UX suser() sets errno. + */ + +-afs_suser(void *credp) ++afs_suser(afs_ucred_t *credp) + { + int save_errno; + int code; +diff --git a/src/afs/NBSD/osi_misc.c b/src/afs/NBSD/osi_misc.c +index dd10f06e6923..2f6f1c40ad3f 100644 +--- a/src/afs/NBSD/osi_misc.c ++++ b/src/afs/NBSD/osi_misc.c +@@ -62,7 +62,7 @@ such damages. + * traditional BSD suser, see OBSD/osi_misc.c. + */ + int +-afs_osi_suser(void *credp) ++afs_osi_suser(afs_ucred_t *credp) + { + int code; + /* +diff --git a/src/afs/OBSD/osi_misc.c b/src/afs/OBSD/osi_misc.c +index 0d17ef14c913..5a7778aa2a1c 100644 +--- a/src/afs/OBSD/osi_misc.c ++++ b/src/afs/OBSD/osi_misc.c +@@ -65,7 +65,7 @@ such damages. + */ + + int +-afs_osi_suser(void *credp) ++afs_osi_suser(afs_ucred_t *credp) + { + #ifdef AFS_OBSD35_ENV + return (suser_ucred((struct ucred *)credp) ? 0 : 1); +diff --git a/src/afs/UKERNEL/afs_usrops.c b/src/afs/UKERNEL/afs_usrops.c +index 13cf64b4eba4..2d11b445a299 100644 +--- a/src/afs/UKERNEL/afs_usrops.c ++++ b/src/afs/UKERNEL/afs_usrops.c +@@ -165,13 +165,13 @@ getf(int fd) + * Every user is a super user + */ + int +-afs_osi_suser(void *credp) ++afs_osi_suser(afs_ucred_t *credp) + { + return 1; + } + + int +-afs_suser(void *credp) ++afs_suser(afs_ucred_t *credp) + { + return 1; + } +diff --git a/src/afs/UKERNEL/osi_machdep.h b/src/afs/UKERNEL/osi_machdep.h +index 161fe7ecf0bd..27667eba6184 100644 +--- a/src/afs/UKERNEL/osi_machdep.h ++++ b/src/afs/UKERNEL/osi_machdep.h +@@ -76,7 +76,7 @@ extern usr_mutex_t afs_global_lock; + + extern int afs_bufferpages; + +-extern int afs_suser(void *credp); ++extern int afs_suser(afs_ucred_t *credp); + + #define setuerror(erval) get_user_struct()->u_error = (erval) + #define getuerror(erval) get_user_struct()->u_error +diff --git a/src/afs/afs_osi.c b/src/afs/afs_osi.c +index 3d34fb5289c9..b2f01f3c5ea2 100644 +--- a/src/afs/afs_osi.c ++++ b/src/afs/afs_osi.c +@@ -264,7 +264,7 @@ shutdown_osisleep(void) + + #if !defined(AFS_OBSD_ENV) && !defined(AFS_NBSD40_ENV) + int +-afs_osi_suser(void *cr) ++afs_osi_suser(afs_ucred_t *cr) + { + #if defined(AFS_SUN510_ENV) + return (priv_policy(cr, PRIV_SYS_SUSER_COMPAT, B_FALSE, EPERM, NULL) == 0); +diff --git a/src/afs/afs_osi_pag.c b/src/afs/afs_osi_pag.c +index f53fc8b14639..f29fea656b64 100644 +--- a/src/afs/afs_osi_pag.c ++++ b/src/afs/afs_osi_pag.c +@@ -139,7 +139,7 @@ static int afs_pag_sleepcnt = 0; + static int afs_pag_timewarn = 0; + + static int +-afs_pag_sleep(afs_ucred_t **acred) ++afs_pag_sleep(afs_ucred_t *acred) + { + int rv = 0; + +@@ -160,7 +160,7 @@ afs_pag_sleep(afs_ucred_t **acred) + } + + static int +-afs_pag_wait(afs_ucred_t **acred) ++afs_pag_wait(afs_ucred_t *acred) + { + int code = 0; + +@@ -197,11 +197,11 @@ afs_setpag(void) + { + + #if defined(AFS_SUN5_ENV) +- afs_ucred_t **acred = *credpp; ++ afs_ucred_t *acred = *credpp; + #elif defined(AFS_OBSD_ENV) +- afs_ucred_t **acred = &p->p_ucred; ++ afs_ucred_t *acred = p->p_ucred; + #else +- afs_ucred_t **acred = NULL; ++ afs_ucred_t *acred = NULL; + #endif + + int code = 0; +@@ -311,11 +311,11 @@ afs_setpag_val(int pagval) + { + + #if defined(AFS_SUN5_ENV) +- afs_ucred_t **acred = *credp; ++ afs_ucred_t *acred = *credp; + #elif defined(AFS_OBSD_ENV) +- afs_ucred_t **acred = &p->p_ucred; ++ afs_ucred_t *acred = p->p_ucred; + #else +- afs_ucred_t **acred = NULL; ++ afs_ucred_t *acred = NULL; + #endif + + int code = 0; +diff --git a/src/afs/afs_pioctl.c b/src/afs/afs_pioctl.c +index 37f383cd3469..3564e1623761 100644 +--- a/src/afs/afs_pioctl.c ++++ b/src/afs/afs_pioctl.c +@@ -5236,7 +5236,7 @@ DECL_PIOCTL(PCallBackAddr) + if (!afs_resourceinit_flag) /* afs deamons havn't started yet */ + return EIO; /* Inappropriate ioctl for device */ + +- if (!afs_osi_suser(acred)) ++ if (!afs_osi_suser(*acred)) + return EACCES; + + if (afs_pd_getInt(ain, &addr) != 0) +@@ -5633,7 +5633,7 @@ DECL_PIOCTL(PNFSNukeCreds) + return EACCES; + } + afs_PutUser(tu, SHARED_LOCK); +- } else if (!afs_osi_suser(acred)) { ++ } else if (!afs_osi_suser(*acred)) { + return EACCES; + } + +diff --git a/src/afs/afs_prototypes.h b/src/afs/afs_prototypes.h +index cc19895640c3..b4d877bd99e7 100644 +--- a/src/afs/afs_prototypes.h ++++ b/src/afs/afs_prototypes.h +@@ -558,7 +558,7 @@ extern void afs_osi_Invisible(void); + extern void shutdown_osi(void); + extern void shutdown_osinet(void); + extern void shutdown_osisleep(void); +-extern int afs_osi_suser(void *credp); ++extern int afs_osi_suser(afs_ucred_t *credp); + extern void afs_osi_TraverseProcTable(void); + #if defined(KERNEL) && !defined(UKERNEL) + extern const afs_ucred_t *afs_osi_proc2cred(afs_proc_t * pr); +-- +2.45.2 + diff -Nru openafs-1.8.9/debian/patches/openafs-sa-2024-001-stable18.patch openafs-1.8.9/debian/patches/openafs-sa-2024-001-stable18.patch --- openafs-1.8.9/debian/patches/openafs-sa-2024-001-stable18.patch 1970-01-01 00:00:00.000000000 +0000 +++ openafs-1.8.9/debian/patches/openafs-sa-2024-001-stable18.patch 2024-12-25 20:19:02.000000000 +0000 @@ -0,0 +1,492 @@ +From 20c22347b41eea2ebbdc0ab15f16c822af44df51 Mon Sep 17 00:00:00 2001 +From: Andrew Deason +Date: Fri, 10 Jan 2020 12:01:50 -0600 +Subject: [PATCH 1/2] 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 + +Reviewed-on: https://gerrit.openafs.org/14090 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +(cherry picked from commit f701f704c7bc93cf5fd7cffaaa043cef6a99e77f) + +Change-Id: I675d6cb111ca74638a3b856a3c989dcb2fe6d534 +Reviewed-on: https://gerrit.openafs.org/15927 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +--- + 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(-) + +--- a/src/afs/AIX/osi_groups.c ++++ b/src/afs/AIX/osi_groups.c +@@ -86,6 +86,14 @@ setpag(cred, pagvalue, newpag, change_pa + 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) { +@@ -99,7 +107,7 @@ setpag(cred, pagvalue, newpag, change_pa + ngroups += 2; + } + #endif +- *newpag = (pagvalue == -1 ? genpag() : pagvalue); ++ *newpag = pagvalue; + #ifdef AFS_AIX51_ENV + if (change_parent) { + code = kcred_setpag(*cred, PAG_AFS, *newpag); +--- a/src/afs/DARWIN/osi_groups.c ++++ b/src/afs/DARWIN/osi_groups.c +@@ -97,6 +97,14 @@ setpag(proc, cred, pagvalue, newpag, cha + 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 */ +@@ -108,7 +116,7 @@ setpag(proc, cred, pagvalue, newpag, cha + } + 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; +--- a/src/afs/FBSD/osi_groups.c ++++ b/src/afs/FBSD/osi_groups.c +@@ -79,6 +79,14 @@ setpag(struct thread *td, struct ucred * + 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) { +@@ -91,7 +99,7 @@ setpag(struct thread *td, struct ucred * + } + 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)); +--- a/src/afs/HPUX/osi_groups.c ++++ b/src/afs/HPUX/osi_groups.c +@@ -70,6 +70,14 @@ setpag(cred, pagvalue, newpag, change_pa + 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 */ +@@ -81,7 +89,7 @@ setpag(cred, pagvalue, newpag, change_pa + } + 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)) { +--- a/src/afs/IRIX/osi_groups.c ++++ b/src/afs/IRIX/osi_groups.c +@@ -276,6 +276,17 @@ setpag(cred, pagvalue, newpag, change_pa + + 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 */ +@@ -291,7 +302,7 @@ setpag(cred, pagvalue, newpag, change_pa + } + 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) +--- a/src/afs/LINUX/osi_groups.c ++++ b/src/afs/LINUX/osi_groups.c +@@ -144,11 +144,19 @@ __setpag(cred_t **cr, afs_uint32 pagvalu + { + 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) { +--- a/src/afs/NBSD/osi_groups.c ++++ b/src/afs/NBSD/osi_groups.c +@@ -83,6 +83,13 @@ setpag(afs_proc_t *proc, afs_ucred_t **c + 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 */ +@@ -94,7 +101,7 @@ setpag(afs_proc_t *proc, afs_ucred_t **c + } + 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; +--- a/src/afs/OBSD/osi_groups.c ++++ b/src/afs/OBSD/osi_groups.c +@@ -80,6 +80,14 @@ setpag(struct proc *proc, struct ucred * + 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 +@@ -101,7 +109,7 @@ setpag(struct proc *proc, struct ucred * + } + 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; +--- a/src/afs/SOLARIS/osi_groups.c ++++ b/src/afs/SOLARIS/osi_groups.c +@@ -165,6 +165,13 @@ setpag(cred, pagvalue, newpag, change_pa + + 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). +@@ -174,8 +181,6 @@ setpag(cred, pagvalue, newpag, change_pa + /* 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); + +--- 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_ + + 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_ + } + 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); +--- 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,18 @@ afs_pag_wait(afs_ucred_t *acred) + return code; + } + ++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; ++} ++ + int + #if defined(AFS_SUN5_ENV) + afs_setpag(afs_ucred_t **credpp) +@@ -205,6 +217,7 @@ afs_setpag(void) + #endif + + int code = 0; ++ afs_uint32 pag; + + #if defined(AFS_SGI53_ENV) && defined(MP) + /* This is our first chance to get the global lock. */ +@@ -213,20 +226,19 @@ afs_setpag(void) + + AFS_STATCNT(afs_setpag); + +- code = afs_pag_wait(acred); ++ 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. + */ +--- a/src/afs/afs_pioctl.c ++++ b/src/afs/afs_pioctl.c +@@ -4665,7 +4665,13 @@ HandleClientContext(struct afs_ioctl *ab + *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 */ + /* +--- a/src/afs/afs_prototypes.h ++++ b/src/afs/afs_prototypes.h +@@ -596,7 +596,7 @@ extern int afs_setpag(afs_proc_t *p, voi + 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); +--- 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 */ \ diff -Nru openafs-1.8.9/debian/patches/openafs-sa-2024-002-stable18.patch openafs-1.8.9/debian/patches/openafs-sa-2024-002-stable18.patch --- openafs-1.8.9/debian/patches/openafs-sa-2024-002-stable18.patch 1970-01-01 00:00:00.000000000 +0000 +++ openafs-1.8.9/debian/patches/openafs-sa-2024-002-stable18.patch 2024-12-25 20:19:02.000000000 +0000 @@ -0,0 +1,1076 @@ +From f74f960a18f559e683d6a1f5104e43c3ca93ecb8 Mon Sep 17 00:00:00 2001 +From: Andrew Deason +Date: Mon, 18 Sep 2023 16:13:57 -0500 +Subject: [PATCH 01/10] 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 + +Reviewed-on: https://gerrit.openafs.org/15908 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +(cherry picked from commit e15decb318797f1d471588dc669c3e3b26f1b8b3) + +Change-Id: I0f447310db5a988b21e19bb5158bb564d4ea3d94 +Reviewed-on: https://gerrit.openafs.org/15929 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +--- + src/viced/afsfileprocs.c | 35 +++++++++++++++++++++++++++++++++++ + 1 file changed, 35 insertions(+) + +diff --git a/src/viced/afsfileprocs.c b/src/viced/afsfileprocs.c +index 31ce69a93d..dad2cd35e4 100644 +--- a/src/viced/afsfileprocs.c ++++ b/src/viced/afsfileprocs.c +@@ -3059,6 +3059,36 @@ SRXAFS_StoreData64(struct rx_call * acall, struct AFSFid * Fid, + return code; + } + ++/** ++ * 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; ++} ++ + afs_int32 + SRXAFS_StoreACL(struct rx_call * acall, struct AFSFid * Fid, + struct AFSOpaque * AccessList, +@@ -3082,6 +3112,11 @@ SRXAFS_StoreACL(struct rx_call * acall, struct AFSFid * Fid, + 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 a07e50726df09c49dfe7b953c3e49eb98f310c09 Mon Sep 17 00:00:00 2001 +From: Andrew Deason +Date: Mon, 18 Sep 2023 16:14:07 -0500 +Subject: [PATCH 02/10] 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 + +Reviewed-on: https://gerrit.openafs.org/15909 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +(cherry picked from commit f4dfc2d7183f126bc4a45b5cabc78c3de020925f) + +Change-Id: If1554aa899542761ec6e6611394f2ee4f9379f22 +Reviewed-on: https://gerrit.openafs.org/15930 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +--- + 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 dad2cd35e4..5df4d6c0cb 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 1e6e813188ecce62eb7af19385d911f63469bdb6 Mon Sep 17 00:00:00 2001 +From: Andrew Deason +Date: Tue, 19 Sep 2023 15:44:08 -0500 +Subject: [PATCH 03/10] OPENAFS-SA-2024-002: acl: Do not parse beyond end of + ACL + +CVE-2024-10396 + +The early parsing code in acl_Internalize_pr() tries to advance +'nextc' to go beyond the first two newlines in the given ACL string. +But if the given ACL string has no newlines, or only 1 newline, then +'nextc' will point beyond the end of the ACL string, potentially +pointing to garbage. + +Intuitively, it may look like the ACL string must contain at least 2 +newlines because we have sscanf()'d the string with "%d\n%\d". +However, whitespace characters in sscanf() are not matched exactly +like non-whitespace characters are; a sequence of whitespace +characters matches any amount of whitespace (including none). So, a +string like "1 2" will be parsed by "%d\n%d\n", but will not contain +any newline characters. + +Usually this should result in a parse error from acl_Internalize_pr(), +but if the garbage happens to parse successfully, this could result in +unrelated memory getting stored to the ACL. + +To fix this, don't advance 'nextc' if we're already at the end of the +ACL string. + +FIXES 135445 + +Reviewed-on: https://gerrit.openafs.org/15910 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +(cherry picked from commit 35d218c1d17973c1412ea5dff1e23d9aae50c4c7) + +Change-Id: I7a7d136676e548adba5fa8d0003b5f8342332a86 +Reviewed-on: https://gerrit.openafs.org/15931 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +--- + src/libacl/aclprocs.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/src/libacl/aclprocs.c b/src/libacl/aclprocs.c +index cd07e813c9..ab6b21dac2 100644 +--- a/src/libacl/aclprocs.c ++++ b/src/libacl/aclprocs.c +@@ -264,10 +264,12 @@ acl_Internalize_pr(int (*func)(namelist *names, idlist *ids), char *elist, struc + nextc = elist; + while (*nextc && *nextc != '\n') + nextc++; +- nextc++; ++ if (*nextc != '\0') ++ nextc++; + while (*nextc && *nextc != '\n') + nextc++; +- nextc++; /* now at the beginning of the entry list */ ++ if (*nextc != '\0') ++ nextc++; /* now at the beginning of the entry list */ + for (i = 0; i < (*acl)->positive; i++) { + int k; + if (sscanf(nextc, "%63s\t%d\n", lnames.namelist_val[i], &k) != 2) { +-- +2.45.2 + + +From d66caf8c04878724001839317637445708edef2c Mon Sep 17 00:00:00 2001 +From: Andrew Deason +Date: Tue, 19 Sep 2023 15:55:42 -0500 +Subject: [PATCH 04/10] OPENAFS-SA-2024-002: acl: Error on missing newlines + when parsing ACL + +CVE-2024-10396 + +In acl_Internalize_pr(), each line in an ACL granting rights (positive +or negative) is sscanf()'d with "%63s\t%d\n", and then we try to +advance 'nextc' beyond the next newline character. + +However, sscanf()'ing "%63s\t%d\n" does not guarantee that there is a +newline in the given string. Whitespace characters in sscanf() are not +matched exactly, and may match any amount of whitespace (including +none at all). For example, a string like "foo 4" may be parsed by +sscanf(), but does not contain any newlines. + +If this happens, strchr(nextc, '\n') will return NULL, and we'll +advance 'nextc' to 0x1, causing a segfault when we next try to +dereference 'nextc'. + +To avoid this, check if 'nextc' is NULL after the strchr() call, and +return an error if so. + +FIXES 135445 + +Reviewed-on: https://gerrit.openafs.org/15911 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +(cherry picked from commit 96ab2c6f8a614d597a523b45871c5f64a50a7040) + +Change-Id: I666dfb2c401410865c1f98d9db1b342b52c8f628 +Reviewed-on: https://gerrit.openafs.org/15932 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +--- + src/libacl/aclprocs.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/src/libacl/aclprocs.c b/src/libacl/aclprocs.c +index ab6b21dac2..fbdfaa6f9c 100644 +--- a/src/libacl/aclprocs.c ++++ b/src/libacl/aclprocs.c +@@ -278,6 +278,10 @@ acl_Internalize_pr(int (*func)(namelist *names, idlist *ids), char *elist, struc + } + (*acl)->entries[i].rights = k; + nextc = strchr(nextc, '\n'); ++ if (nextc == NULL) { ++ free(lnames.namelist_val); ++ return (-1); ++ } + nextc++; /* 1 + index can cast ptr to integer */ + } + j = i; +@@ -290,6 +294,10 @@ acl_Internalize_pr(int (*func)(namelist *names, idlist *ids), char *elist, struc + return (-1); + } + nextc = strchr(nextc, '\n'); ++ if (nextc == NULL) { ++ free(lnames.namelist_val); ++ return (-1); ++ } + nextc++; + } + lids.idlist_len = 0; +-- +2.45.2 + + +From ee020f7cba7d82bc3d4b468210b5052af53c5db5 Mon Sep 17 00:00:00 2001 +From: Andrew Deason +Date: Wed, 21 Aug 2024 00:29:34 -0500 +Subject: [PATCH 05/10] OPENAFS-SA-2024-002: viced: Introduce 'rawACL' in + StoreACL + +CVE-2024-10396 + +Change our StoreACL implementation to refer to the 'AccessList' argument +via a new local variable called 'rawACL'. This makes it clearer to +users that the data is a string, and makes it easier for future commits +to make sure we don't access the 'AccessList' argument in certain +situations. + +Update almost all users in StoreACL to refer to 'rawACL' instead of +'AccessList'. Change the name of 'AccessList' to 'uncheckedACL' to make +sure we don't miss any users. Update our check_acl() call to use +'uncheckedACL' (and not 'rawACL'), because it must use an AFSOpaque to +check the ACL. + +Change RXStore_AccessList() and printableACL() to accept a plain char* +instead of a struct AFSOpaque. + +This commit should not incur any noticeable behavior change. Technically +printableACL() is changed to run strlen() on the given string, but this +should not cause any noticeable change in behavior: + +This change could cause printableACL() to process less of the string +than before, if the string contains a NUL byte before the end of the +AFSOpaque buffer. But this doesn't matter, since the all of our code +after this treats the ACL as a plain string, and so doesn't look at any +data beyond the first NUL. It's not possible for printableACL() to +process more data than before, because check_acl() has already checked +that the ACL string contains a NUL byte, so we must process +AFSOpaque_len bytes or fewer. + +FIXES 135445 + +Reviewed-on: https://gerrit.openafs.org/15912 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +(cherry picked from commit eb8b93a971c6293cdfbf8cd3d9a6351a8cb76f81) + +[1.8: printableACL() does not exist in this branch.] + +Change-Id: I65b518acab26be0bb1854c29e46c90e5fee52d41 +Reviewed-on: https://gerrit.openafs.org/15933 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +--- + src/viced/afsfileprocs.c | 15 ++++++++------- + 1 file changed, 8 insertions(+), 7 deletions(-) + +diff --git a/src/viced/afsfileprocs.c b/src/viced/afsfileprocs.c +index 5df4d6c0cb..77c6165e7b 100644 +--- a/src/viced/afsfileprocs.c ++++ b/src/viced/afsfileprocs.c +@@ -1246,12 +1246,12 @@ RXFetch_AccessList(Vnode * targetptr, Vnode * parentwhentargetnotdir, + * the target dir's vnode storage. + */ + static afs_int32 +-RXStore_AccessList(Vnode * targetptr, struct AFSOpaque *AccessList) ++RXStore_AccessList(Vnode * targetptr, char *AccessList) + { + int code; + struct acl_accessList *newACL = NULL; + +- if (acl_Internalize_pr(hpr_NameToId, AccessList->AFSOpaque_val, &newACL) ++ if (acl_Internalize_pr(hpr_NameToId, AccessList, &newACL) + != 0) { + code = EINVAL; + goto done; +@@ -3099,7 +3099,7 @@ check_acl(struct AFSOpaque *AccessList) + + afs_int32 + SRXAFS_StoreACL(struct rx_call * acall, struct AFSFid * Fid, +- struct AFSOpaque * AccessList, ++ struct AFSOpaque *uncheckedACL, + struct AFSFetchStatus * OutStatus, struct AFSVolSync * Sync) + { + Vnode *targetptr = 0; /* pointer to input fid */ +@@ -3114,13 +3114,14 @@ SRXAFS_StoreACL(struct rx_call * acall, struct AFSFid * Fid, + struct client *t_client = NULL; /* tmp ptr to client data */ + struct in_addr logHostAddr; /* host ip holder for inet_ntoa */ + struct fsstats fsstats; ++ char *rawACL = uncheckedACL->AFSOpaque_val; + + fsstats_StartOp(&fsstats, FS_STATS_RPCIDX_STOREACL); + + if ((errorCode = CallPreamble(acall, ACTIVECALL, Fid, &tcon, &thost))) + goto Bad_StoreACL; + +- errorCode = check_acl(AccessList); ++ errorCode = check_acl(uncheckedACL); + if (errorCode != 0) { + goto Bad_StoreACL; + } +@@ -3130,7 +3131,7 @@ SRXAFS_StoreACL(struct rx_call * acall, struct AFSFid * Fid, + logHostAddr.s_addr = rxr_HostOf(tcon); + ViceLog(1, + ("SAFS_StoreACL, Fid = %u.%u.%u, ACL=%s, Host %s:%d, Id %d\n", +- Fid->Volume, Fid->Vnode, Fid->Unique, AccessList->AFSOpaque_val, ++ Fid->Volume, Fid->Vnode, Fid->Unique, rawACL, + inet_ntoa(logHostAddr), ntohs(rxr_PortOf(tcon)), t_client->z.ViceId)); + FS_LOCK; + AFSCallStats.StoreACL++, AFSCallStats.TotalCalls++; +@@ -3159,7 +3160,7 @@ SRXAFS_StoreACL(struct rx_call * acall, struct AFSFid * Fid, + } + + /* Build and store the new Access List for the dir */ +- if ((errorCode = RXStore_AccessList(targetptr, AccessList))) { ++ if ((errorCode = RXStore_AccessList(targetptr, rawACL))) { + goto Bad_StoreACL; + } + +@@ -3186,7 +3187,7 @@ SRXAFS_StoreACL(struct rx_call * acall, struct AFSFid * Fid, + + osi_auditU(acall, StoreACLEvent, errorCode, + AUD_ID, t_client ? t_client->z.ViceId : 0, +- AUD_FID, Fid, AUD_ACL, AccessList->AFSOpaque_val, AUD_END); ++ AUD_FID, Fid, AUD_ACL, rawACL, AUD_END); + return errorCode; + + } /*SRXAFS_StoreACL */ +-- +2.45.2 + + +From bb01d76a2095baa65880bdc5d504e7a198958265 Mon Sep 17 00:00:00 2001 +From: Andrew Deason +Date: Wed, 21 Aug 2024 00:41:49 -0500 +Subject: [PATCH 06/10] OPENAFS-SA-2024-002: viced: Avoid unchecked ACL in + StoreACL audit log + +CVE-2024-10396 + +Currently in SRXAFS_StoreACL, if CallPreamble() or check_acl() fail, we +will jump to Bad_StoreACL, which will pass the ACL string from the +client to osi_auditU. Since check_acl() hasn't yet checked if the given +ACL contains a NUL byte, the ACL may be an unterminated string. If +auditing is enabled, this can cause garbage to be logged to the audit +log, or cause the fileserver to crash. + +To avoid this, set 'rawACL' to NULL at first, only setting it to the +actual ACL string after check_acl() has succeeded. This ensures that all +code accessing 'rawACL' is guaranteed to be using a terminated string. + +This may mean that we pass a NULL AUD_ACL to osi_auditU. Our auditing +code explicitly checks for and handles handles NULL strings, so this is +fine. + +FIXES 135445 + +Reviewed-on: https://gerrit.openafs.org/15913 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +(cherry picked from commit c9eae1e8b26144063e5d1db23d47ee82c4b9ef3a) + +Change-Id: Ieda6f910d875c4b5179011e5e93e5694d3f4ce47 +Reviewed-on: https://gerrit.openafs.org/15934 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +--- + src/viced/afsfileprocs.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/src/viced/afsfileprocs.c b/src/viced/afsfileprocs.c +index 77c6165e7b..5e6655fa26 100644 +--- a/src/viced/afsfileprocs.c ++++ b/src/viced/afsfileprocs.c +@@ -3114,7 +3114,7 @@ SRXAFS_StoreACL(struct rx_call * acall, struct AFSFid * Fid, + struct client *t_client = NULL; /* tmp ptr to client data */ + struct in_addr logHostAddr; /* host ip holder for inet_ntoa */ + struct fsstats fsstats; +- char *rawACL = uncheckedACL->AFSOpaque_val; ++ char *rawACL = NULL; + + fsstats_StartOp(&fsstats, FS_STATS_RPCIDX_STOREACL); + +@@ -3125,6 +3125,7 @@ SRXAFS_StoreACL(struct rx_call * acall, struct AFSFid * Fid, + if (errorCode != 0) { + goto Bad_StoreACL; + } ++ rawACL = uncheckedACL->AFSOpaque_val; + + /* Get ptr to client data for user Id for logging */ + t_client = (struct client *)rx_GetSpecific(tcon, rxcon_client_key); +-- +2.45.2 + + +From 64068705b15661a8d4e0b9f9f2ad4aec34ed51a7 Mon Sep 17 00:00:00 2001 +From: Benjamin Kaduk +Date: Mon, 4 Nov 2024 20:33:16 -0800 +Subject: [PATCH 07/10] OPENAFS-SA-2024-002: verify FetchACL returned a valid + string + +CVE-2024-10396 + +Analogously to how a call to RXAFS_StoreACL() with a malformed +ACL string can cause a fileserver to perform invalid memory operations, +a malformed ACL string returned in response to a call to RXAFS_FetchACL() +can cause a client to perform invalid memory operations. + +Modify all the in-tree callers of the RPC to verify that the ACL +data, which is conveyed as an XDR 'opaque' but whose contents +are actually expected to be a string, is a valid C string. If +a zero-length opaque or one without a trailing NUL is received, +treat that as an error response from the fileserver rather than +returning success. + +The Unix cache manager's pioctl handler already has logic to cope with a +zero-length reply by emitting a single NUL byte to userspace. This +special-casing seems to have been in place from the original IBM import, +though it does so by confusingly "skipping over" a NUL byte already put +in place. For historical compatibility, preserve that behavior rather +than treating the zero-length reply as an error as we do for the other +callers. It seems likely that this location should treat a zero-length +reply as an error just as the other call sites do, but that can be done +as a later change. + +Reviewed-on: https://gerrit.openafs.org/15914 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +(cherry picked from commit 0b1ccb0dbc3b7673558eceff3d672971f5bb0197) + +Change-Id: Ifbce762d76641f08b5fc5e79b4c8dad07c1a135a +Reviewed-on: https://gerrit.openafs.org/15935 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +--- + src/WINNT/afsd/cm_ioctl.c | 4 ++++ + src/afs/afs_pioctl.c | 10 ++++++++-- + src/libafscp/afscp_acl.c | 6 ++++++ + 3 files changed, 18 insertions(+), 2 deletions(-) + +diff --git a/src/afs/afs_pioctl.c b/src/afs/afs_pioctl.c +index 172f8aa519..efb884c59e 100644 +--- a/src/afs/afs_pioctl.c ++++ b/src/afs/afs_pioctl.c +@@ -1607,10 +1607,16 @@ DECL_PIOCTL(PGetAcl) + SHARED_LOCK, NULL)); + + if (code == 0) { +- if (acl.AFSOpaque_len == 0) ++ if (acl.AFSOpaque_len == 0) { + afs_pd_skip(aout, 1); /* leave the NULL */ +- else ++ ++ } else if (memchr(acl.AFSOpaque_val, '\0', acl.AFSOpaque_len) == NULL) { ++ /* Do not return an unterminated ACL string. */ ++ code = EINVAL; ++ ++ } else { + afs_pd_skip(aout, acl.AFSOpaque_len); /* Length of the ACL */ ++ } + } + return code; + } +diff --git a/src/libafscp/afscp_acl.c b/src/libafscp/afscp_acl.c +index 596d0e436d..9f93271317 100644 +--- a/src/libafscp/afscp_acl.c ++++ b/src/libafscp/afscp_acl.c +@@ -58,6 +58,12 @@ afscp_FetchACL(const struct afscp_venusfid *dir, struct AFSOpaque *acl) + code = RXAFS_FetchACL(server->conns[j], &df, acl, &dfst, &vs); + if (code >= 0) + break; ++ /* Zero-length or non-string ACLs are malformed. */ ++ if (acl->AFSOpaque_len == 0 || memchr(acl->AFSOpaque_val, '\0', ++ acl->AFSOpaque_len) == NULL) { ++ code = EIO; ++ break; ++ } + } + } + if (code >= 0) +-- +2.45.2 + + +From a96a3160f5425125588f39f5ac612df3ef9b9a8a Mon Sep 17 00:00:00 2001 +From: Benjamin Kaduk +Date: Mon, 4 Nov 2024 20:50:50 -0800 +Subject: [PATCH 08/10] OPENAFS-SA-2024-002: verify FetchACL returned only a + string + +CVE-2024-10396 + +Supplement the previous commit by additionally verifying that +the returned ACL string occupies the entire XDR opaque, rejecting +any values returned that have an internal NUL prior to the end +of the opaque. + +Reviewed-on: https://gerrit.openafs.org/15915 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +(cherry picked from commit 7e13414e8ea995d438cde3e60988225f3ab4cbcd) + +Change-Id: I107f89e3d8a5c3c5cd67f6296742bfca7cace0e1 +Reviewed-on: https://gerrit.openafs.org/15936 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +--- + src/WINNT/afsd/cm_ioctl.c | 3 +++ + src/afs/afs_pioctl.c | 4 ++++ + src/libafscp/afscp_acl.c | 4 ++++ + 3 files changed, 11 insertions(+) + +diff --git a/src/afs/afs_pioctl.c b/src/afs/afs_pioctl.c +index efb884c59e..95202f020c 100644 +--- a/src/afs/afs_pioctl.c ++++ b/src/afs/afs_pioctl.c +@@ -1614,6 +1614,10 @@ DECL_PIOCTL(PGetAcl) + /* Do not return an unterminated ACL string. */ + code = EINVAL; + ++ } else if (strlen(acl.AFSOpaque_val) + 1 != acl.AFSOpaque_len) { ++ /* Do not return an ACL string that has data beyond the trailing NUL. */ ++ code = EINVAL; ++ + } else { + afs_pd_skip(aout, acl.AFSOpaque_len); /* Length of the ACL */ + } +diff --git a/src/libafscp/afscp_acl.c b/src/libafscp/afscp_acl.c +index 9f93271317..f9cd3740dc 100644 +--- a/src/libafscp/afscp_acl.c ++++ b/src/libafscp/afscp_acl.c +@@ -64,6 +64,10 @@ afscp_FetchACL(const struct afscp_venusfid *dir, struct AFSOpaque *acl) + code = EIO; + break; + } ++ if (strlen(acl->AFSOpaque_val) + 1 != acl->AFSOpaque_len) { ++ code = EIO; ++ break; ++ } + } + } + if (code >= 0) +-- +2.45.2 + + +From a9ede52673b8c8abbfc2577ac6987a8a5686206f Mon Sep 17 00:00:00 2001 +From: Benjamin Kaduk +Date: Mon, 4 Nov 2024 20:50:50 -0800 +Subject: [PATCH 09/10] OPENAFS-SA-2024-002: make VIOCGETAL consumers stay + within string bounds + +CVE-2024-10396 + +After the preceding commits, the data returned by the VIOCGETAL +pioctl (a RXAFS_FetchAcl wrapper) will safely be NUL-terminated. +However, the callers that attempt to parse the ACL string make +assumptions that the returned data will be properly formatted, +and implement a "skip to next line" functionality (under various +names) that blindly increments a char* until it finds a newline +character, which can read past the end of even a properly +NUL-terminated string if there is not a newline where one is +expected. + +Adjust the various "skip to next line" functionality to keep +the current string pointer at the trailing NUL if the end of the +string is reached while searching for a newline. + +Reviewed-on: https://gerrit.openafs.org/15916 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +(cherry picked from commit a4ecb050540528a1bff840ff08d21f99e6ef3fbf) + +Change-Id: Id2d8c0164cfaa7d03a9e37b29ff58b88cf815483 +Reviewed-on: https://gerrit.openafs.org/15937 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +--- + src/WINNT/afsd/fs_acl.c | 5 +++-- + src/kauth/kkids.c | 2 +- + src/kauth/test/test_interim_ktc.c | 5 +++-- + src/libadmin/client/afs_clientAdmin.c | 6 ++++++ + src/sys/rmtsysnet.c | 5 +++-- + src/uss/uss_acl.c | 5 +++-- + src/venus/fs.c | 5 +++-- + 7 files changed, 22 insertions(+), 11 deletions(-) + +diff --git a/src/kauth/kkids.c b/src/kauth/kkids.c +index d5437f152f..ebc4c331e6 100644 +--- a/src/kauth/kkids.c ++++ b/src/kauth/kkids.c +@@ -190,7 +190,7 @@ find_me(char *arg, char *parent_dir) + return 1; /* found it */ + } + +-#define SkipLine(str) { while (*str !='\n') str++; str++; } ++#define SkipLine(str) do { while (*str != '\0' && *str !='\n') str++; if (*str == '\n') str++; } while(0) + + /* this function returns TRUE (1) if the file is in AFS, otherwise false (0) */ + static int +diff --git a/src/kauth/test/test_interim_ktc.c b/src/kauth/test/test_interim_ktc.c +index 58cf061fd5..8af9035187 100644 +--- a/src/kauth/test/test_interim_ktc.c ++++ b/src/kauth/test/test_interim_ktc.c +@@ -385,9 +385,10 @@ char * + SkipLine(astr) + char *astr; + { +- while (*astr != '\n') ++ while (*astr != '\0' && *astr != '\n') ++ astr++; ++ if (*astr == '\n') + astr++; +- astr++; + return astr; + } + +diff --git a/src/libadmin/client/afs_clientAdmin.c b/src/libadmin/client/afs_clientAdmin.c +index d0f61f1063..e4c9a7d426 100644 +--- a/src/libadmin/client/afs_clientAdmin.c ++++ b/src/libadmin/client/afs_clientAdmin.c +@@ -1535,9 +1535,13 @@ afsclient_ACLEntryAdd(const char *directory, const char *user, + sscanf(old_acl_string, "%d dfs:%d %1024s", &cur_acl.nplus, &cur_acl.dfs, + cur_acl.cell); + ptr = strchr(old_acl_string, '\n'); ++ if (ptr == NULL) ++ goto fail_afsclient_ACLEntryAdd; + ptr++; + sscanf(ptr, "%d", &cur_acl.nminus); + ptr = strchr(ptr, '\n'); ++ if (ptr == NULL) ++ goto fail_afsclient_ACLEntryAdd; + ptr++; + if (is_dfs == 3) { + tst = ADMMISCNODFSACL; +@@ -1564,6 +1568,8 @@ afsclient_ACLEntryAdd(const char *directory, const char *user, + + if (strcmp(cur_user, user)) { + ptr = strchr(ptr, '\n'); ++ if (ptr == NULL) ++ goto fail_afsclient_ACLEntryAdd; + ptr++; + sprintf(tmp, "%s %d\n", cur_user, cur_user_acl); + strcat(new_acl_string, tmp); +diff --git a/src/sys/rmtsysnet.c b/src/sys/rmtsysnet.c +index da6b3e64e4..bdb5c0d72a 100644 +--- a/src/sys/rmtsysnet.c ++++ b/src/sys/rmtsysnet.c +@@ -55,9 +55,10 @@ struct ClearToken { + char * + RSkipLine(char *astr) + { +- while (*astr != '\n') ++ while (*astr != '\0' && *astr != '\n') ++ astr++; ++ if (*astr == '\n') + astr++; +- astr++; + return astr; + } + +diff --git a/src/uss/uss_acl.c b/src/uss/uss_acl.c +index 003eff8b93..b3450ba2b2 100644 +--- a/src/uss/uss_acl.c ++++ b/src/uss/uss_acl.c +@@ -339,9 +339,10 @@ static char * + SkipLine(char *a_str) + { /*SkipLine */ + +- while (*a_str != '\n') ++ while (*a_str != '\0' && *a_str != '\n') ++ a_str++; ++ if (*a_str == '\n') + a_str++; +- a_str++; + return (a_str); + + } /*SkipLine */ +diff --git a/src/venus/fs.c b/src/venus/fs.c +index ac16bb43b1..32fa73fc33 100644 +--- a/src/venus/fs.c ++++ b/src/venus/fs.c +@@ -553,9 +553,10 @@ PruneList(struct AclEntry **ae, int dfs) + static char * + SkipLine(char *astr) + { +- while (*astr != '\n') ++ while (*astr != '\0' && *astr != '\n') ++ astr++; ++ if (*astr == '\n') + astr++; +- astr++; + return astr; + } + +-- +2.45.2 + + +From 21941c0ab2d28fa3a074f46e4d448d518a7c1b8a Mon Sep 17 00:00:00 2001 +From: Andrew Deason +Date: Tue, 5 Nov 2024 23:40:24 -0600 +Subject: [PATCH 10/10] OPENAFS-SA-2024-002: Avoid uninitialized memory when + parsing ACLs + +CVE-2024-10396 + +Several places in the tree parse ACLs using sscanf() calls that look +similar to this: + + sscanf(str, "%d dfs:%d %s", &nplus, &dfs, cell); + sscanf(str, "%100s %d", tname, &trights); + +Some callers check whether the scanf() returns negative or 0, but some +callers do not check the return code at all. If only some of the fields +are present in the sscanf()'d string (because, for instance, the ACL is +malformed), some of the arguments are left alone, and may be set to +garbage if the relevant variable was never initialized. + +If the parsed ACL is copied to another ACL, this can result in the +copied ACL containing uninitialized memory. + +To avoid this, make sure all of the variables passed to sscanf() and +similar calls are initialized before parsing. This commit does not +guarantee that the results make sense, but at least the results do not +contain uninitialized memory. + +Reviewed-on: https://gerrit.openafs.org/15917 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +(cherry picked from commit ac602a0a5624b0f0ab04df86f618d09f2a4ad063) + +Change-Id: I00245c12993683eb3b58d51cf77742f758bac120 +Reviewed-on: https://gerrit.openafs.org/15938 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +--- + src/WINNT/afsd/fs_acl.c | 10 +++++----- + src/kauth/kkids.c | 4 ++-- + src/kauth/test/test_interim_ktc.c | 10 +++++----- + src/libadmin/client/afs_clientAdmin.c | 3 +++ + src/sys/rmtsysnet.c | 8 ++++---- + src/uss/uss_acl.c | 8 ++++---- + src/venus/fs.c | 12 ++++++------ + 7 files changed, 29 insertions(+), 26 deletions(-) + +diff --git a/src/kauth/kkids.c b/src/kauth/kkids.c +index ebc4c331e6..7e9043fdd6 100644 +--- a/src/kauth/kkids.c ++++ b/src/kauth/kkids.c +@@ -228,7 +228,7 @@ struct AclEntry { + static struct Acl * + ParseAcl(char *astr) + { +- int nplus, nminus, i, trights; ++ int nplus = 0, nminus = 0, i, trights = 0; + char tname[MAXNAME + 1] = ""; + struct AclEntry *first, *last, *tl; + struct Acl *ta; +@@ -237,7 +237,7 @@ ParseAcl(char *astr) + sscanf(astr, "%d", &nminus); + SkipLine(astr); + +- ta = malloc(sizeof(struct Acl)); ++ ta = calloc(sizeof(*ta), 1); + ta->nplus = nplus; + + last = 0; +diff --git a/src/kauth/test/test_interim_ktc.c b/src/kauth/test/test_interim_ktc.c +index 8af9035187..bc58f8668b 100644 +--- a/src/kauth/test/test_interim_ktc.c ++++ b/src/kauth/test/test_interim_ktc.c +@@ -396,8 +396,8 @@ struct Acl * + ParseAcl(astr) + char *astr; + { +- int nplus, nminus, i, trights; +- char tname[MAXNAME]; ++ int nplus = 0, nminus = 0, i, trights = 0; ++ char tname[MAXNAME + 1] = ""; + struct AclEntry *first, *last, *tl; + struct Acl *ta; + sscanf(astr, "%d", &nplus); +@@ -405,7 +405,7 @@ ParseAcl(astr) + sscanf(astr, "%d", &nminus); + astr = SkipLine(astr); + +- ta = malloc(sizeof(struct Acl)); ++ ta = calloc(sizeof(*ta), 1); + ta->nplus = nplus; + ta->nminus = nminus; + +@@ -414,7 +414,7 @@ ParseAcl(astr) + for (i = 0; i < nplus; i++) { + sscanf(astr, "%100s %d", tname, &trights); + astr = SkipLine(astr); +- tl = malloc(sizeof(struct AclEntry)); ++ tl = calloc(sizeof(*tl), 1); + if (!first) + first = tl; + strcpy(tl->name, tname); +@@ -431,7 +431,7 @@ ParseAcl(astr) + for (i = 0; i < nminus; i++) { + sscanf(astr, "%100s %d", tname, &trights); + astr = SkipLine(astr); +- tl = malloc(sizeof(struct AclEntry)); ++ tl = calloc(sizeof(*tl), 1); + if (!first) + first = tl; + strcpy(tl->name, tname); +diff --git a/src/libadmin/client/afs_clientAdmin.c b/src/libadmin/client/afs_clientAdmin.c +index e4c9a7d426..b485f48e7c 100644 +--- a/src/libadmin/client/afs_clientAdmin.c ++++ b/src/libadmin/client/afs_clientAdmin.c +@@ -1435,6 +1435,9 @@ afsclient_ACLEntryAdd(const char *directory, const char *user, + char tmp[64 + 35]; + int is_dfs; + ++ memset(&cur_acl, 0, sizeof(cur_acl)); ++ memset(cur_user, 0, sizeof(cur_user)); ++ + if (client_init == 0) { + tst = ADMCLIENTNOINIT; + goto fail_afsclient_ACLEntryAdd; +diff --git a/src/sys/rmtsysnet.c b/src/sys/rmtsysnet.c +index bdb5c0d72a..1e6911f59a 100644 +--- a/src/sys/rmtsysnet.c ++++ b/src/sys/rmtsysnet.c +@@ -66,7 +66,7 @@ RSkipLine(char *astr) + struct Acl * + RParseAcl(char *astr) + { +- int nplus, nminus, i, trights; ++ int nplus = 0, nminus = 0, i, trights = 0; + char tname[MAXNAME + 1] = ""; + struct AclEntry *first, *last, *tl; + struct Acl *ta; +@@ -75,7 +75,7 @@ RParseAcl(char *astr) + sscanf(astr, "%d", &nminus); + astr = RSkipLine(astr); + +- ta = malloc(sizeof(struct Acl)); ++ ta = calloc(sizeof(*ta), 1); + ta->nplus = nplus; + ta->nminus = nminus; + +@@ -84,7 +84,7 @@ RParseAcl(char *astr) + for (i = 0; i < nplus; i++) { + sscanf(astr, "%" opr_stringize(MAXNAME) "s %d", tname, &trights); + astr = RSkipLine(astr); +- tl = malloc(sizeof(struct AclEntry)); ++ tl = calloc(sizeof(*tl), 1); + if (!first) + first = tl; + strcpy(tl->name, tname); +@@ -101,7 +101,7 @@ RParseAcl(char *astr) + for (i = 0; i < nminus; i++) { + sscanf(astr, "%" opr_stringize(MAXNAME) "s %d", tname, &trights); + astr = RSkipLine(astr); +- tl = malloc(sizeof(struct AclEntry)); ++ tl = calloc(sizeof(*tl), 1); + if (!first) + first = tl; + strcpy(tl->name, tname); +diff --git a/src/uss/uss_acl.c b/src/uss/uss_acl.c +index b3450ba2b2..0eb4b53bca 100644 +--- a/src/uss/uss_acl.c ++++ b/src/uss/uss_acl.c +@@ -405,7 +405,7 @@ static struct Acl * + ParseAcl(char *a_str) + { /*ParseAcl */ + +- int nplus, nminus, i, trights; ++ int nplus = 0, nminus = 0, i, trights = 0; + char tname[MAXNAME + 1] = ""; + struct AclEntry *first, *last, *tl; + struct Acl *ta; +@@ -422,7 +422,7 @@ ParseAcl(char *a_str) + /* + * Allocate and initialize the first entry. + */ +- ta = malloc(sizeof(struct Acl)); ++ ta = calloc(sizeof(*ta), 1); + ta->nplus = nplus; + ta->nminus = nminus; + +@@ -434,7 +434,7 @@ ParseAcl(char *a_str) + for (i = 0; i < nplus; i++) { + sscanf(a_str, "%" opr_stringize(MAXNAME) "s %d", tname, &trights); + a_str = SkipLine(a_str); +- tl = malloc(sizeof(struct AclEntry)); ++ tl = calloc(sizeof(*tl), 1); + if (!first) + first = tl; + strcpy(tl->name, tname); +@@ -454,7 +454,7 @@ ParseAcl(char *a_str) + for (i = 0; i < nminus; i++) { + sscanf(a_str, "%" opr_stringize(MAXNAME) "s %d", tname, &trights); + a_str = SkipLine(a_str); +- tl = malloc(sizeof(struct AclEntry)); ++ tl = calloc(sizeof(*tl), 1); + if (!first) + first = tl; + strcpy(tl->name, tname); +diff --git a/src/venus/fs.c b/src/venus/fs.c +index 32fa73fc33..fa914a4c42 100644 +--- a/src/venus/fs.c ++++ b/src/venus/fs.c +@@ -573,7 +573,7 @@ EmptyAcl(char *astr) + struct Acl *tp; + int junk; + +- tp = malloc(sizeof(struct Acl)); ++ tp = calloc(sizeof(*tp), 1); + assert(tp); + tp->nplus = tp->nminus = 0; + tp->pluslist = tp->minuslist = 0; +@@ -585,12 +585,12 @@ EmptyAcl(char *astr) + static struct Acl * + ParseAcl(char *astr) + { +- int nplus, nminus, i, trights; +- char tname[MAXNAME]; ++ int nplus = 0, nminus = 0, i, trights = 0; ++ char tname[MAXNAME + 1] = ""; + struct AclEntry *first, *last, *tl; + struct Acl *ta; + +- ta = malloc(sizeof(struct Acl)); ++ ta = calloc(sizeof(*ta), 1); + assert(ta); + ta->dfs = 0; + sscanf(astr, "%d dfs:%d %1024s", &ta->nplus, &ta->dfs, ta->cell); +@@ -606,7 +606,7 @@ ParseAcl(char *astr) + for (i = 0; i < nplus; i++) { + sscanf(astr, "%99s %d", tname, &trights); + astr = SkipLine(astr); +- tl = malloc(sizeof(struct AclEntry)); ++ tl = calloc(sizeof(*tl), 1); + assert(tl); + if (!first) + first = tl; +@@ -624,7 +624,7 @@ ParseAcl(char *astr) + for (i = 0; i < nminus; i++) { + sscanf(astr, "%99s %d", tname, &trights); + astr = SkipLine(astr); +- tl = malloc(sizeof(struct AclEntry)); ++ tl = calloc(sizeof(*tl), 1); + assert(tl); + if (!first) + first = tl; +-- +2.45.2 + diff -Nru openafs-1.8.9/debian/patches/openafs-sa-2024-003-stable18.patch openafs-1.8.9/debian/patches/openafs-sa-2024-003-stable18.patch --- openafs-1.8.9/debian/patches/openafs-sa-2024-003-stable18.patch 1970-01-01 00:00:00.000000000 +0000 +++ openafs-1.8.9/debian/patches/openafs-sa-2024-003-stable18.patch 2024-12-25 20:19:02.000000000 +0000 @@ -0,0 +1,1617 @@ +From fec84e347768080e4370e5aeb05886bfe19ae54b Mon Sep 17 00:00:00 2001 +From: Michael Meffie +Date: Fri, 10 Mar 2023 17:51:17 -0500 +Subject: [PATCH 1/9] xdr: Avoid xdr_string maxsize check when freeing + +The maxsize argument in xdr_string() is garbage when called by +xdr_free(), since xdr_free() only passes the XDR handle and the xdr +string to be freed. Sometimes the size check fails and xdr_string() +returns early, without freeing the string and without setting the object +pointer to NULL. + +Usually this just results in leaking the string's memory. But since +commit 9ae5b599c7 (bos: Let xdr allocate rpc output strings), many +callers in bos.c rely on xdr_free(xdr_string) to set the given string +to NULL; if this doesn't happen, subsequent calls to BOZO_ RPCs can +corrupt memory, often causing the 'bos' process to segfault. + +We only need the maxsize check when encoding or decoding, so avoid +accessing the maxsize agument when the op mode is XDR_FREE. + +In general, xdr_free() can only safely be used on xdr 2-argument xdr +functions, so must be avoided when freeing xdr opaque, byte, and union +types. + +This change makes it safe to use xdr_free() to free xdr strings, but in +the future, we should provide a typesafe and less fragile function for +freeing xdr strings returned from RPCs. Currently, xdr_free(xdr_string) +is only called by the bos client and the tests. + +Reviewed-on: https://gerrit.openafs.org/15343 +Tested-by: BuildBot +Reviewed-by: Benjamin Kaduk +(cherry picked from commit bbb1e8adfed6804ac6fbae0a073dc6927096e16a) + +Change-Id: I1f190d28acab5fa1621919f283571fcacb495ce4 +Reviewed-on: https://gerrit.openafs.org/15939 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +--- + src/rx/xdr.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/rx/xdr.c b/src/rx/xdr.c +index bfc1b7f6fe..88f7772c99 100644 +--- a/src/rx/xdr.c ++++ b/src/rx/xdr.c +@@ -530,7 +530,7 @@ xdr_string(XDR * xdrs, char **cpp, u_int maxsize) + if (!xdr_u_int(xdrs, &size)) { + return (FALSE); + } +- if (size > maxsize) { ++ if (xdrs->x_op != XDR_FREE && size > maxsize) { + return (FALSE); + } + nodesize = size + 1; +-- +2.45.2 + + +From 40440c3eb628ff1772588bdc99d7496292097bbd Mon Sep 17 00:00:00 2001 +From: Andrew Deason +Date: Thu, 13 Jun 2024 15:28:38 -0500 +Subject: [PATCH 2/9] OPENAFS-SA-2024-003: xdr: Avoid prealloc'd string OUT + args + +CVE-2024-10397 + +Currently, several callers call RPCs with string OUT arguments, and +provide preallocated memory for those arguments. This can easily allow a +response from the server to overrun the allocated buffer, stomping over +stack or heap memory. + +We could simply make our preallocated buffers larger than the maximum +size that the RPC allows, but relying on that is error prone, and +there's no way for XDR to check if a string buffer is large enough. + +Instead, to make sure we don't overrun a given preallocated buffer, +avoid giving a preallocated buffer to such RPCs, and let XDR allocate +the memory for us. + +Specifically, this commit changes several callers to +RXAFS_GetVolumeStatus(), and one caller of BOZO_GetInstanceParm(), to +avoid passing in a preallocated string buffer. + +All other callers of RPCs with string OUT args already let XDR allocate +the buffers for them. + +FIXES 135043 + +Reviewed-on: https://gerrit.openafs.org/15918 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +(cherry picked from commit 00a1b266af51a828a022c23e7bb006a39740eaad) + +Change-Id: Ib174d008eaf1fd10d42702bcdb607e45b26acf58 +Reviewed-on: https://gerrit.openafs.org/15940 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +--- + src/WINNT/afsd/cm_ioctl.c | 26 +++++++++++++++++----- + src/WINNT/afsd/cm_vnodeops.c | 18 +++++++-------- + src/WINNT/afsd/cm_volume.c | 19 ++++++++-------- + src/WINNT/afsrdr/user/RDRFunction.c | 34 ++++++++++++++--------------- + src/afs/afs_pioctl.c | 20 ++++++++--------- + src/libadmin/bos/afs_bosAdmin.c | 16 +++++++++++--- + 6 files changed, 76 insertions(+), 57 deletions(-) + +diff --git a/src/afs/afs_pioctl.c b/src/afs/afs_pioctl.c +index 95202f020c..baa3182ffb 100644 +--- a/src/afs/afs_pioctl.c ++++ b/src/afs/afs_pioctl.c +@@ -1994,32 +1994,31 @@ DECL_PIOCTL(PSetTokens) + */ + DECL_PIOCTL(PGetVolumeStatus) + { +- char volName[32]; +- char *offLineMsg = afs_osi_Alloc(256); +- char *motd = afs_osi_Alloc(256); ++ char *volName = NULL; ++ char *offLineMsg = NULL; ++ char *motd = NULL; + struct afs_conn *tc; + afs_int32 code = 0; + struct AFSFetchVolumeStatus volstat; +- char *Name; + struct rx_connection *rxconn; + XSTATS_DECLS; + +- osi_Assert(offLineMsg != NULL); +- osi_Assert(motd != NULL); + AFS_STATCNT(PGetVolumeStatus); + if (!avc) { + code = EINVAL; + goto out; + } +- Name = volName; + do { + tc = afs_Conn(&avc->f.fid, areq, SHARED_LOCK, &rxconn); + if (tc) { + XSTATS_START_TIME(AFS_STATS_FS_RPCIDX_GETVOLUMESTATUS); + RX_AFS_GUNLOCK(); ++ xdr_free((xdrproc_t) xdr_string, &volName); ++ xdr_free((xdrproc_t) xdr_string, &offLineMsg); ++ xdr_free((xdrproc_t) xdr_string, &motd); + code = + RXAFS_GetVolumeStatus(rxconn, avc->f.fid.Fid.Volume, &volstat, +- &Name, &offLineMsg, &motd); ++ &volName, &offLineMsg, &motd); + RX_AFS_GLOCK(); + XSTATS_END_TIME; + } else +@@ -2040,8 +2039,9 @@ DECL_PIOCTL(PGetVolumeStatus) + if (afs_pd_putString(aout, motd) != 0) + return E2BIG; + out: +- afs_osi_Free(offLineMsg, 256); +- afs_osi_Free(motd, 256); ++ xdr_free((xdrproc_t) xdr_string, &volName); ++ xdr_free((xdrproc_t) xdr_string, &offLineMsg); ++ xdr_free((xdrproc_t) xdr_string, &motd); + return code; + } + +diff --git a/src/libadmin/bos/afs_bosAdmin.c b/src/libadmin/bos/afs_bosAdmin.c +index 76436c0496..8ec0129770 100644 +--- a/src/libadmin/bos/afs_bosAdmin.c ++++ b/src/libadmin/bos/afs_bosAdmin.c +@@ -1289,6 +1289,8 @@ bos_ProcessNotifierGet(const void *serverHandle, const char *processName, + int rc = 0; + afs_status_t tst = 0; + bos_server_p b_handle = (bos_server_p) serverHandle; ++ char *tnotif = NULL; ++ size_t len; + + if (!isValidServerHandle(b_handle, &tst)) { + goto fail_bos_ProcessNotifierGet; +@@ -1305,14 +1307,22 @@ bos_ProcessNotifierGet(const void *serverHandle, const char *processName, + } + + tst = BOZO_GetInstanceParm(b_handle->server, (char *)processName, +- 999, ¬ifier); ++ 999, &tnotif); ++ if (tst != 0) { ++ goto fail_bos_ProcessNotifierGet; ++ } + +- if (tst == 0) { +- rc = 1; ++ len = strlcpy(notifier, tnotif, BOS_MAX_NAME_LEN); ++ if (len >= BOS_MAX_NAME_LEN) { ++ tst = ADMRPCTOOBIG; ++ goto fail_bos_ProcessNotifierGet; + } + ++ rc = 1; ++ + fail_bos_ProcessNotifierGet: + ++ xdr_free((xdrproc_t)xdr_string, &tnotif); + if (st != NULL) { + *st = tst; + } +-- +2.45.2 + + +From c18640c6b98b10cd6f78c63195ff822689cb5348 Mon Sep 17 00:00:00 2001 +From: Andrew Deason +Date: Thu, 13 Jun 2024 15:30:50 -0500 +Subject: [PATCH 3/9] OPENAFS-SA-2024-003: xdr: Set _len for prealloc'd + opaque/array OUT args + +CVE-2024-10397 + +Currently, a few RPCs with arrays or opaque OUT arguments are called +with preallocated memory for the arg, but also provide a _len of 0 (or +an uninitialized _len). This makes it impossible for the xdr routine to +tell whether we have allocated enough space to actually hold the +response from the server. + +To help this situation, either specify an appropriate _len for the +preallocated value (cm_IoctlGetACL, fsprobe_LWP), or don't provide a +preallocated buffer at all and let xdr allocate a buffer for us +(PGetAcl). + +Note that this commit doesn't change xdr to actually check the value of +the given _len; but now a future commit can do so without breaking +callers. + +FIXES 135043 + +Reviewed-on: https://gerrit.openafs.org/15919 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +(cherry picked from commit b2b1110ddd9e19670dbc6a3217dc2a74af432f82) + +Change-Id: Ibdee49b79da1476c4e606bcad5fb3d08eb259ad7 +Reviewed-on: https://gerrit.openafs.org/15941 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +--- + src/WINNT/afsd/cm_ioctl.c | 2 +- + src/afs/afs_pioctl.c | 8 ++++---- + src/fsprobe/fsprobe.c | 1 + + 3 files changed, 6 insertions(+), 5 deletions(-) + +diff --git a/src/afs/afs_pioctl.c b/src/afs/afs_pioctl.c +index baa3182ffb..5337986c4e 100644 +--- a/src/afs/afs_pioctl.c ++++ b/src/afs/afs_pioctl.c +@@ -1590,11 +1590,10 @@ DECL_PIOCTL(PGetAcl) + return ERANGE; + Fid.Vnode |= (ain->remaining << 30); + } +- acl.AFSOpaque_val = aout->ptr; ++ memset(&acl, 0, sizeof(acl)); + do { + tconn = afs_Conn(&avc->f.fid, areq, SHARED_LOCK, &rxconn); + if (tconn) { +- acl.AFSOpaque_val[0] = '\0'; + XSTATS_START_TIME(AFS_STATS_FS_RPCIDX_FETCHACL); + RX_AFS_GUNLOCK(); + code = RXAFS_FetchACL(rxconn, &Fid, &acl, &OutStatus, &tsync); +@@ -1608,7 +1607,7 @@ DECL_PIOCTL(PGetAcl) + + if (code == 0) { + if (acl.AFSOpaque_len == 0) { +- afs_pd_skip(aout, 1); /* leave the NULL */ ++ code = afs_pd_putBytes(aout, "\0", 1); /* leave a single NUL byte */ + + } else if (memchr(acl.AFSOpaque_val, '\0', acl.AFSOpaque_len) == NULL) { + /* Do not return an unterminated ACL string. */ +@@ -1619,9 +1618,10 @@ DECL_PIOCTL(PGetAcl) + code = EINVAL; + + } else { +- afs_pd_skip(aout, acl.AFSOpaque_len); /* Length of the ACL */ ++ code = afs_pd_putBytes(aout, acl.AFSOpaque_val, acl.AFSOpaque_len); + } + } ++ xdr_free((xdrproc_t) xdr_AFSOpaque, &acl); + return code; + } + +diff --git a/src/fsprobe/fsprobe.c b/src/fsprobe/fsprobe.c +index f29a56fdd9..18a03cad1e 100644 +--- a/src/fsprobe/fsprobe.c ++++ b/src/fsprobe/fsprobe.c +@@ -223,6 +223,7 @@ fsprobe_LWP(void *unused) + struct ProbeViceStatistics *curr_stats; /*Current stats region */ + int *curr_probeOK; /*Current probeOK field */ + ViceStatistics64 stats64; /*Current stats region */ ++ stats64.ViceStatistics64_len = STATS64_VERSION; + stats64.ViceStatistics64_val = malloc(STATS64_VERSION * + sizeof(afs_uint64)); + while (1) { /*Service loop */ +-- +2.45.2 + + +From d253a52d3b59bd691eae8863ea2f06d99ad18550 Mon Sep 17 00:00:00 2001 +From: Andrew Deason +Date: Sun, 4 Oct 2020 23:04:06 -0500 +Subject: [PATCH 4/9] OPENAFS-SA-2024-003: xdr: Prevent XDR_DECODE buffer + overruns + +CVE-2024-10397 + +When making an RPC call from a client, output arguments that use +arrays (or array-like objects like strings and opaques) can be +allocated by XDR, like so: + +{ + struct idlist ids; + + ids.idlist_val = NULL; + ids.idlist_len = 0; + code = PR_NameToID(rxconn, names, &ids); + /* data inside ids.idlist_val[...] */ + xdr_free((xdrproc_t) xdr_idlist, &ids); +} + +With this approach, during XDR_DECODE, xdr_array() reads in the number +of array elements from the peer, then allocates enough memory to hold +that many elements, and then reads in the array elements. + +Alternatively, the caller can provide preallocated memory, like so: + +{ + struct idlist ids; + afs_int32 ids_buf[30]; + + ids.idlist_val = ids_buf; + ids.idlist_len = 30; + code = PR_NameToID(rxconn, names, &ids); + /* data inside ids.idlist_val[...] */ +} + +With this approach, during XDR_DECODE, xdr_array() reads in the number +of array elements from the peer, and then reads in the array elements +into the supplied buffer. However, in this case, xdr_array() never +checks that the number of array elements will actually fit into the +supplied buffer; the _len field provided by the caller is just ignored. +In this example, if the ptserver responds with 50 elements for the 'ids' +output argument, xdr_array() will write 50 afs_int32's into +'ids.idlist_val', going beyond the end of the 30 elements that are +actually allocated. + +It's also possible, and in fact very easy, to use xdr-allocated +buffers and then reuse them as a preallocated buffer, possibly +accidentally. For example: + +{ + struct idlist ids; + + ids.idlist_val = NULL; + ids.idlist_len = 0; + while (some_condition) { + code = PR_NameToID(rxconn, names, &ids); + } +} + +In this case, the first call to PR_NameToID can cause the buffer for +'ids' to be allocated by XDR, which will then be reused by the +subsequent calls to PR_NameToId. Note that this can happen even if the +first PR_NameToID call fails; the call can be aborted after the output +array is allocated. + +Retrying an RPC in this way is effectively what all ubik_Call* +codepaths do (including all ubik_* wrappers, e.g. ubik_PR_NameToID). +Or some callers retry effectively the same RPC when falling back to +earlier versions (e.g. VL_ListAttributesN2 -> VL_ListAttributesN). + +To prevent this for arrays and opaques, change xdr_array (and +xdr_bytes) to check if the _len field for preallocated buffers is +large enough, and return failure if it's not. + +Also perform the same check for the ka_CBS and ka_BBS structures. These +are mostly the same as opaques, but they have custom serialization +functions in src/kauth/kaaux.c. ka_BBS also has two lengths: the actual +length of bytes, and a 'max' length. ka_CBS isn't used for any RPC +output arguments, but fix it for consistency. + +For strings, the situation is complicated by the fact that callers +cannot pass in how much space was allocated for the string, since +callers only provide a char**. So for strings, just refuse to use a +preallocated buffer at all, and return failure if one is provided. + +Note that for some callers using preallocated arrays or strings, the +described buffer overruns are not possible, since the preallocated +buffers are larger than the max length specified in the relevant +RPC-L. For example, afs_DoBulkStat() allocates AFSCBMAX entries for +the output args for RXAFS_InlineBulkStatus, which is the max length +specified in the RPC-L, so a buffer overrun is impossible. But since +it is so easy to allow a buffer overrun, enforce the length checks for +everyone. + +FIXES 135043 + +Reviewed-on: https://gerrit.openafs.org/15920 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +(cherry picked from commit 13413eceed80d106cbed5ffb91c4dfbc8cccf55c) + +Change-Id: I1010d2fa309d4a441ebaf285168c2e7e887753b9 +Reviewed-on: https://gerrit.openafs.org/15942 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +--- + src/kauth/kaaux.c | 33 +++++++++++++++++++++++++++++++-- + src/rx/xdr.c | 36 +++++++++++++++++++++++++++++++----- + src/rx/xdr_array.c | 24 +++++++++++++++++++++--- + src/rx/xdr_arrayn.c | 24 +++++++++++++++++++++--- + 4 files changed, 104 insertions(+), 13 deletions(-) + +diff --git a/src/kauth/kaaux.c b/src/kauth/kaaux.c +index eea1402a24..e951af52a2 100644 +--- a/src/kauth/kaaux.c ++++ b/src/kauth/kaaux.c +@@ -34,7 +34,17 @@ xdr_ka_CBS(XDR * x, struct ka_CBS *abbs) + xdr_afs_int32(x, &len); + if (len < 0 || len > MAXBS) + return FALSE; +- if (!abbs->SeqBody) ++ if (abbs->SeqBody != NULL) { ++ if (len > abbs->SeqLen) { ++ /* ++ * We've been given a preallocated buffer to decode into, but ++ * we're decoding 'len' bytes, which is larger than the ++ * provided buffer (only abbs->SeqLen bytes large). This won't ++ * work. ++ */ ++ return FALSE; ++ } ++ } else + abbs->SeqBody = malloc(len); + abbs->SeqLen = len; + xdr_opaque(x, abbs->SeqBody, len); +@@ -61,7 +71,26 @@ xdr_ka_BBS(XDR * x, struct ka_BBS *abbs) + if (!xdr_afs_int32(x, &maxLen) || !xdr_afs_int32(x, &len) || (len < 0) + || (len > MAXBS) || (len > maxLen)) + return FALSE; +- if (!abbs->SeqBody) ++ if (abbs->SeqBody != NULL) { ++ if (len > abbs->MaxSeqLen) { ++ /* ++ * We've been given a preallocated buffer to decode into, but ++ * we're decoding 'len' bytes, which is larger than the ++ * provided buffer (only abbs->MaxSeqLen bytes large). This ++ * won't work. ++ */ ++ return FALSE; ++ } ++ if (maxLen > abbs->MaxSeqLen) { ++ /* ++ * Our preallocated buffer only has space for MaxSeqLen bytes. ++ * Don't let the peer change 'abbs' so that it looks like we ++ * have space for more bytes than that; that could cause us to ++ * access memory beyond what we've actually allocated. ++ */ ++ return FALSE; ++ } ++ } else + abbs->SeqBody = malloc(maxLen); + abbs->MaxSeqLen = maxLen; + abbs->SeqLen = len; +diff --git a/src/rx/xdr.c b/src/rx/xdr.c +index 88f7772c99..ef31d455ac 100644 +--- a/src/rx/xdr.c ++++ b/src/rx/xdr.c +@@ -404,10 +404,28 @@ xdr_bytes(XDR * xdrs, char **cpp, u_int * sizep, + /* + * first deal with the length since xdr bytes are counted + */ +- if (!xdr_u_int(xdrs, sizep)) { +- return (FALSE); ++ ++ if (xdrs->x_op == XDR_DECODE && sp != NULL) { ++ /* ++ * We've been given a preallocated array to decode into. Before we ++ * modify *sizep, check that we have enough space to fit the elements ++ * that follow. ++ */ ++ if (!xdr_u_int(xdrs, &nodesize)) { ++ return FALSE; ++ } ++ if (nodesize > *sizep) { ++ return FALSE; ++ } ++ *sizep = nodesize; ++ ++ } else { ++ if (!xdr_u_int(xdrs, sizep)) { ++ return (FALSE); ++ } ++ nodesize = *sizep; + } +- nodesize = *sizep; ++ + if ((nodesize > maxsize) && (xdrs->x_op != XDR_FREE)) { + return (FALSE); + } +@@ -541,8 +559,16 @@ xdr_string(XDR * xdrs, char **cpp, u_int maxsize) + switch (xdrs->x_op) { + + case XDR_DECODE: +- if (sp == NULL) +- *cpp = sp = (char *)osi_alloc(nodesize); ++ if (sp != NULL) { ++ /* ++ * Refuse to use preallocated strings, since we cannot verify ++ * whether enough memory has been allocated to handle the decoded ++ * string. ++ */ ++ return FALSE; ++ } ++ ++ *cpp = sp = (char *)osi_alloc(nodesize); + if (sp == NULL) { + return (FALSE); + } +diff --git a/src/rx/xdr_array.c b/src/rx/xdr_array.c +index 1785019922..bfbd429db8 100644 +--- a/src/rx/xdr_array.c ++++ b/src/rx/xdr_array.c +@@ -93,10 +93,28 @@ xdr_array(XDR * xdrs, caddr_t * addrp, u_int * sizep, u_int maxsize, + maxsize = i; + + /* like strings, arrays are really counted arrays */ +- if (!xdr_u_int(xdrs, sizep)) { +- return (FALSE); ++ ++ if (xdrs->x_op == XDR_DECODE && target != NULL) { ++ /* ++ * We've been given a preallocated array to decode into. Before we ++ * modify *sizep, check that we have enough space to fit the elements ++ * that follow. ++ */ ++ if (!xdr_u_int(xdrs, &c)) { ++ return FALSE; ++ } ++ if (c > *sizep) { ++ return FALSE; ++ } ++ *sizep = c; ++ ++ } else { ++ if (!xdr_u_int(xdrs, sizep)) { ++ return (FALSE); ++ } ++ c = *sizep; + } +- c = *sizep; ++ + if ((c > maxsize) && (xdrs->x_op != XDR_FREE)) { + return (FALSE); + } +diff --git a/src/rx/xdr_arrayn.c b/src/rx/xdr_arrayn.c +index d6cb1f7d12..80d1dfa054 100644 +--- a/src/rx/xdr_arrayn.c ++++ b/src/rx/xdr_arrayn.c +@@ -93,10 +93,28 @@ xdr_arrayN(XDR * xdrs, caddr_t * addrp, u_int * sizep, u_int maxsize, + maxsize = i; + + /* like strings, arrays are really counted arrays */ +- if (!xdr_u_int(xdrs, sizep)) { +- return (FALSE); ++ ++ if (xdrs->x_op == XDR_DECODE && target != NULL) { ++ /* ++ * We've been given a preallocated array to decode into. Before we ++ * modify *sizep, check that we have enough space to fit the elements ++ * that follow. ++ */ ++ if (!xdr_u_int(xdrs, &c)) { ++ return FALSE; ++ } ++ if (c > *sizep) { ++ return FALSE; ++ } ++ *sizep = c; ++ ++ } else { ++ if (!xdr_u_int(xdrs, sizep)) { ++ return (FALSE); ++ } ++ c = *sizep; + } +- c = *sizep; ++ + if ((c > maxsize) && (xdrs->x_op != XDR_FREE)) { + return (FALSE); + } +-- +2.45.2 + + +From 0ff2cd9e0f5656e8327c5fe47935998de3669678 Mon Sep 17 00:00:00 2001 +From: Andrew Deason +Date: Thu, 15 Oct 2020 23:18:53 -0500 +Subject: [PATCH 5/9] OPENAFS-SA-2024-003: Check sanity on lengths of RPC + returned arrays + +CVE-2024-10397 + +Various RPCs return a variable-length array in an OUT argument, but +are only supposed to return specific sizes. A few instances of this +include the following (but this is not an exhaustive list): + +- AFSVolListOneVolume should only return a single volintInfo. + +- PR_NameToID should return the same number of IDs as names given. + +- VL_GetAddrsU should return the same number of addresses as the + 'nentries' OUT argument. + +Some callers of these RPCs just assume that the server has not +violated these rules. If the server responds with a nonsensical array +size, this could cause us to read beyond the end of the array, or +cause a NULL dereference or other errors. + +For example, some callers of VL_GetAddrsU will iterate over 'nentries' +addresses, even if the 'blkaddrs' OUT argument contains fewer entries. +Or with AFSVolListOneVolume, some callers assume that at least 1 +volintInfo has been returned; if 0 have been returned, we can try to +access a NULL array. + +To avoid all of this, add various sanity checks on the relevant +returned lengths of these RPCs. For most cases, if the lengths are not +sane, return an internal error from the appropriate subsystem (or +RXGEN_CC_UNMARSHAL if there isn't one). For VL_GetAddrsU, if +'nentries' is too long, just set it to the length of the returned +array. + +FIXES 135043 + +Reviewed-on: https://gerrit.openafs.org/15921 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +(cherry picked from commit c732715e4ee78ed1e2414c813ae5a4b3574107a0) + +Change-Id: I2cfc0723f4c3a2692238fa1e59145aceee17e0d6 +Reviewed-on: https://gerrit.openafs.org/15943 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +--- + src/WINNT/afsd/cm_getaddrs.c | 2 ++ + src/WINNT/afsd/cm_vnodeops.c | 7 +++++ + src/afs/afs_volume.c | 4 +++ + src/bucoord/ubik_db_if.c | 6 ++++ + src/butc/dump.c | 18 ++++++++--- + src/libadmin/adminutil/afs_utilAdmin.c | 12 ++++++-- + src/libadmin/pts/afs_ptsAdmin.c | 4 +++ + src/libadmin/vos/afs_vosAdmin.c | 8 +++++ + src/libadmin/vos/vosutils.c | 6 ++++ + src/libadmin/vos/vsprocs.c | 22 +++++++++++--- + src/libafscp/afscp_server.c | 7 +++++ + src/ptserver/ptuser.c | 6 ++++ + src/venus/cacheout.c | 4 +++ + src/viced/host.c | 4 +++ + src/vlserver/vlclient.c | 21 +++++++++++++ + src/volser/vsprocs.c | 42 +++++++++++++++++--------- + src/volser/vsutils.c | 3 ++ + 17 files changed, 151 insertions(+), 25 deletions(-) + +diff --git a/src/afs/afs_volume.c b/src/afs/afs_volume.c +index d3d4d00ff1..0600ccbfaf 100644 +--- a/src/afs/afs_volume.c ++++ b/src/afs/afs_volume.c +@@ -1225,6 +1225,10 @@ LockAndInstallUVolumeEntry(struct volume *av, struct uvldbentry *ve, int acell, + return; + } + ++ if (addrs.bulkaddrs_len < nentries) { ++ nentries = addrs.bulkaddrs_len; ++ } ++ + addrp = addrs.bulkaddrs_val; + for (k = 0; k < nentries; k++) { + addrp[k] = htonl(addrp[k]); +diff --git a/src/bucoord/ubik_db_if.c b/src/bucoord/ubik_db_if.c +index be58e3bbb7..9a05459410 100644 +--- a/src/bucoord/ubik_db_if.c ++++ b/src/bucoord/ubik_db_if.c +@@ -120,6 +120,12 @@ afs_int32 bcdb_listDumps (afs_int32 sflags, afs_int32 groupId, + free(dumpsList.budb_dumpsList_val); + if (flagsList.budb_dumpsList_val) + free(flagsList.budb_dumpsList_val); ++ ++ if (code == 0 && ++ dumpsPtr->budb_dumpsList_len != flagsPtr->budb_dumpsList_len) { ++ code = BUDB_INTERNALERROR; ++ } ++ + return (code); + } + +diff --git a/src/butc/dump.c b/src/butc/dump.c +index 8139d572ce..365544ec7b 100644 +--- a/src/butc/dump.c ++++ b/src/butc/dump.c +@@ -173,6 +173,18 @@ Bind(afs_uint32 server) + return (curr_fromconn); + } + ++static int ++ListOneVolume(struct rx_connection *aconn, afs_int32 apart, afs_uint32 avolid, ++ volEntries *entries) ++{ ++ afs_int32 code; ++ code = AFSVolListOneVolume(aconn, apart, avolid, entries); ++ if (code == 0 && entries->volEntries_len != 1) { ++ code = VOLSERFAILEDOP; ++ } ++ return code; ++} ++ + /* notes + * 1) save the chunksize or otherwise ensure tape space remaining is + * check frequently enough +@@ -217,8 +229,7 @@ dumpVolume(struct tc_dumpDesc * curDump, struct dumpRock * dparamsPtr) + /* Determine when the volume was last cloned and updated */ + volumeInfo.volEntries_val = (volintInfo *) 0; + volumeInfo.volEntries_len = 0; +- rc = AFSVolListOneVolume(fromconn, curDump->partition, curDump->vid, +- &volumeInfo); ++ rc = ListOneVolume(fromconn, curDump->partition, curDump->vid, &volumeInfo); + if (rc) + ERROR_EXIT(rc); + updatedate = volumeInfo.volEntries_val[0].updateDate; +@@ -543,8 +554,7 @@ xbsaDumpVolume(struct tc_dumpDesc * curDump, struct dumpRock * dparamsPtr) + /* Determine when the volume was last cloned and updated */ + volumeInfo.volEntries_val = (volintInfo *) 0; + volumeInfo.volEntries_len = 0; +- rc = AFSVolListOneVolume(fromconn, curDump->partition, curDump->vid, +- &volumeInfo); ++ rc = ListOneVolume(fromconn, curDump->partition, curDump->vid, &volumeInfo); + if (rc) + ERROR_EXIT(rc); + updatedate = volumeInfo.volEntries_val[0].updateDate; +diff --git a/src/libadmin/adminutil/afs_utilAdmin.c b/src/libadmin/adminutil/afs_utilAdmin.c +index 20da3320b7..3afb5cc9be 100644 +--- a/src/libadmin/adminutil/afs_utilAdmin.c ++++ b/src/libadmin/adminutil/afs_utilAdmin.c +@@ -2322,6 +2322,9 @@ util_CMClientConfig(struct rx_connection *conn, afs_ClientConfig_p config, + afs_uint32 allocbytes; + struct cacheConfig tconfig; + ++ tconfig.cacheConfig_val = NULL; ++ tconfig.cacheConfig_len = 0; ++ + if (conn == NULL) { + tst = ADMRXCONNNULL; + goto fail_util_CMClientConfig; +@@ -2333,8 +2336,6 @@ util_CMClientConfig(struct rx_connection *conn, afs_ClientConfig_p config, + } + + config->clientVersion = AFS_CLIENT_RETRIEVAL_VERSION; +- tconfig.cacheConfig_val = NULL; +- tconfig.cacheConfig_len = 0; + tst = + RXAFSCB_GetCacheConfig(conn, config->clientVersion, + &config->serverVersion, &allocbytes, &tconfig); +@@ -2343,12 +2344,17 @@ util_CMClientConfig(struct rx_connection *conn, afs_ClientConfig_p config, + goto fail_util_CMClientConfig; + } + ++ if (tconfig.cacheConfig_len != sizeof(cm_initparams_v1)/sizeof(afs_uint32)) { ++ tst = RXGEN_CC_UNMARSHAL; ++ goto fail_util_CMClientConfig; ++ } ++ + UnmarshallCMClientConfig(config->serverVersion, tconfig.cacheConfig_val, + &config->c); + rc = 1; +- free(tconfig.cacheConfig_val); + + fail_util_CMClientConfig: ++ free(tconfig.cacheConfig_val); + + if (st != NULL) { + *st = tst; +diff --git a/src/libadmin/pts/afs_ptsAdmin.c b/src/libadmin/pts/afs_ptsAdmin.c +index 80f7d4ccff..97c44ce09d 100644 +--- a/src/libadmin/pts/afs_ptsAdmin.c ++++ b/src/libadmin/pts/afs_ptsAdmin.c +@@ -125,6 +125,10 @@ TranslatePTSNames(const afs_cell_handle_p cellHandle, namelist * names, + goto fail_TranslatePTSNames; + } + ++ if (ids->idlist_len != names->namelist_len) { ++ tst = ADMPTSFAILEDNAMETRANSLATE; ++ goto fail_TranslatePTSNames; ++ } + + /* + * Check to see if the lookup failed +diff --git a/src/libadmin/vos/afs_vosAdmin.c b/src/libadmin/vos/afs_vosAdmin.c +index c93875d5d7..8231ca0028 100644 +--- a/src/libadmin/vos/afs_vosAdmin.c ++++ b/src/libadmin/vos/afs_vosAdmin.c +@@ -1217,6 +1217,10 @@ GetServerRPC(void *rpc_specific, int slot, int *last_item, + goto fail_GetServerRPC; + } + ++ if (addr_multi.bulkaddrs_len < total_multi) { ++ total_multi = addr_multi.bulkaddrs_len; ++ } ++ + /* + * Remove any bogus IP addresses which the user may have + * been unable to remove. +@@ -1368,6 +1372,10 @@ vos_FileServerGetBegin(const void *cellHandle, vos_MessageCallBack_t callBack, + goto fail_vos_FileServerGetBegin; + } + ++ if (serv->addresses.bulkaddrs_len < serv->total_addresses) { ++ serv->total_addresses = serv->addresses.bulkaddrs_len; ++ } ++ + /* + * Remove any bogus IP addresses which the user may have + * been unable to remove. +diff --git a/src/libadmin/vos/vosutils.c b/src/libadmin/vos/vosutils.c +index 1b99d2b551..eb788b20ff 100644 +--- a/src/libadmin/vos/vosutils.c ++++ b/src/libadmin/vos/vosutils.c +@@ -324,6 +324,9 @@ VLDB_ListAttributesN2(afs_cell_handle_p cellHandle, + nextindexp); + if (!tst) { + rc = 1; ++ if (blkentriesp->nbulkentries_len < *nentriesp) { ++ *nentriesp = blkentriesp->nbulkentries_len; ++ } + } + + if (st != NULL) { +@@ -365,6 +368,9 @@ VLDB_IsSameAddrs(afs_cell_handle_p cellHandle, afs_int32 serv1, + *equal = 0; + goto fail_VLDB_IsSameAddrs; + } ++ if (addrs.bulkaddrs_len < nentries) { ++ nentries = addrs.bulkaddrs_len; ++ } + + addrp = addrs.bulkaddrs_val; + for (i = 0; i < nentries; i++, addrp++) { +diff --git a/src/libadmin/vos/vsprocs.c b/src/libadmin/vos/vsprocs.c +index 081825fe9b..ab39cd2b36 100644 +--- a/src/libadmin/vos/vsprocs.c ++++ b/src/libadmin/vos/vsprocs.c +@@ -600,7 +600,7 @@ UV_MoveVolume(afs_cell_handle_p cellHandle, afs_uint32 afromvol, + */ + volumeInfo.volEntries_val = (volintInfo *) 0; /*this hints the stub to allocate space */ + volumeInfo.volEntries_len = 0; +- tst = AFSVolListOneVolume(fromconn, afrompart, afromvol, &volumeInfo); ++ tst = ListOneVolume(fromconn, afrompart, afromvol, &volumeInfo); + if (tst) { + goto fail_UV_MoveVolume; + } +@@ -1162,6 +1162,18 @@ UV_BackupVolume(afs_cell_handle_p cellHandle, afs_int32 aserver, + return rc; + } + ++static int ++ListOneVolume(struct rx_connection *aconn, afs_int32 apart, afs_uint32 avolid, ++ volEntries *entries) ++{ ++ afs_int32 code; ++ code = AFSVolListOneVolume(aconn, apart, avolid, entries); ++ if (code == 0 && entries->volEntries_len != 1) { ++ code = VOLSERFAILEDOP; ++ } ++ return code; ++} ++ + static int + DelVol(struct rx_connection *conn, afs_uint32 vid, afs_int32 part, + afs_int32 flags) +@@ -1314,7 +1326,7 @@ VolumeExists(afs_cell_handle_p cellHandle, afs_int32 server, + if (conn) { + volumeInfo.volEntries_val = (volintInfo *) 0; + volumeInfo.volEntries_len = 0; +- tst = AFSVolListOneVolume(conn, partition, volumeid, &volumeInfo); ++ tst = ListOneVolume(conn, partition, volumeid, &volumeInfo); + if (volumeInfo.volEntries_val) + free(volumeInfo.volEntries_val); + if (tst == VOLSERILLEGAL_PARTITION) { +@@ -2773,7 +2785,9 @@ UV_XListOneVolume(struct rx_connection *server, afs_int32 a_partID, + volumeXInfo.volXEntries_len = 0; + + tst = AFSVolXListOneVolume(server, a_partID, a_volID, &volumeXInfo); +- ++ if (tst == 0 && volumeXInfo.volXEntries_len != 1) { ++ tst = VOLSERFAILEDOP; ++ } + if (tst) { + goto fail_UV_XListOneVolume; + } else { +@@ -2833,7 +2847,7 @@ int UV_ListOneVolume(struct rx_connection *server, afs_int32 a_partID, + volumeInfo.volEntries_val = (volintInfo *) 0; + volumeInfo.volEntries_len = 0; + +- tst = AFSVolListOneVolume(server, a_partID, a_volID, &volumeInfo); ++ tst = ListOneVolume(server, a_partID, a_volID, &volumeInfo); + + if (tst) { + goto fail_UV_ListOneVolume; +diff --git a/src/libafscp/afscp_server.c b/src/libafscp/afscp_server.c +index 726d2e5a3b..af1fb4bb43 100644 +--- a/src/libafscp/afscp_server.c ++++ b/src/libafscp/afscp_server.c +@@ -305,6 +305,9 @@ afscp_ServerById(struct afscp_cell *thecell, afsUUID * u) + if (code != 0) { + return NULL; + } ++ if (addrs.bulkaddrs_len < nentries) { ++ nentries = addrs.bulkaddrs_len; ++ } + if (nentries > AFS_MAXHOSTS) { + nentries = AFS_MAXHOSTS; + /* XXX I don't want to do *that* much dynamic allocation */ +@@ -405,6 +408,10 @@ afscp_ServerByAddr(struct afscp_cell *thecell, afs_uint32 addr) + afsUUID_to_string(&uuid, s, 511); + afs_dprintf(("GetServerByAddr 0x%x -> uuid %s\n", addr, s)); + ++ if (addrs.bulkaddrs_len < nentries) { ++ nentries = addrs.bulkaddrs_len; ++ } ++ + if (nentries > AFS_MAXHOSTS) { + nentries = AFS_MAXHOSTS; + /* XXX I don't want to do *that* much dynamic allocation */ +diff --git a/src/ptserver/ptuser.c b/src/ptserver/ptuser.c +index 7df92916a1..1eb7c5c500 100644 +--- a/src/ptserver/ptuser.c ++++ b/src/ptserver/ptuser.c +@@ -521,6 +521,9 @@ pr_NameToId(namelist *names, idlist *ids) + stolower(names->namelist_val[i]); + } + code = ubik_PR_NameToID(pruclient, 0, names, ids); ++ if (code == 0 && ids->idlist_len != names->namelist_len) { ++ code = PRINTERNAL; ++ } + return code; + } + +@@ -566,6 +569,9 @@ string_PR_IDToName(struct ubik_client *client, afs_int32 flags, + code = ubik_PR_IDToName(client, flags, ids, names); + if (code) + return code; ++ if (names->namelist_len != ids->idlist_len) { ++ return PRINTERNAL; ++ } + for (i = 0; i < names->namelist_len; i++) { + code = check_length(names->namelist_val[i]); + if (code) +diff --git a/src/venus/cacheout.c b/src/venus/cacheout.c +index 656bceb57b..810bea3b91 100644 +--- a/src/venus/cacheout.c ++++ b/src/venus/cacheout.c +@@ -71,6 +71,10 @@ ListServers(void) + return 1; + } + ++ if (addrs.bulkaddrs_len < server_count) { ++ server_count = addrs.bulkaddrs_len; ++ } ++ + for (i = 0; i < server_count; ++i) { + ip = addrs.bulkaddrs_val[i]; + +diff --git a/src/viced/host.c b/src/viced/host.c +index f2ce790ba3..1adc7aa199 100644 +--- a/src/viced/host.c ++++ b/src/viced/host.c +@@ -30,6 +30,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -403,6 +404,9 @@ hpr_NameToId(namelist *names, idlist *ids) + for (i = 0; i < names->namelist_len; i++) + stolower(names->namelist_val[i]); + code = ubik_PR_NameToID(uclient, 0, names, ids); ++ if (code == 0 && ids->idlist_len != names->namelist_len) { ++ code = PRINTERNAL; ++ } + return code; + } + +diff --git a/src/vlserver/vlclient.c b/src/vlserver/vlclient.c +index 20c14a5c1f..8426e7f680 100644 +--- a/src/vlserver/vlclient.c ++++ b/src/vlserver/vlclient.c +@@ -561,6 +561,9 @@ handleit(struct cmd_syndesc *as, void *arock) + printf("VL_ListAttributes returned code = %d\n", code); + continue; + } ++ if (entries.bulkentries_len < nentries) { ++ nentries = entries.bulkentries_len; ++ } + entry = (struct vldbentry *)entries.bulkentries_val; + for (i = 0; i < nentries; i++, entry++) + display_entry(entry, 0); +@@ -596,6 +599,9 @@ handleit(struct cmd_syndesc *as, void *arock) + code); + break; + } ++ if (entries.nbulkentries_len < nentries) { ++ nentries = entries.nbulkentries_len; ++ } + + t += nentries; + entry = (struct nvldbentry *)entries.nbulkentries_val; +@@ -764,6 +770,9 @@ handleit(struct cmd_syndesc *as, void *arock) + printf("VL_GetAddrs returned code = %d\n", code); + continue; + } ++ if (addrs.bulkaddrs_len < nentries) { ++ nentries = addrs.bulkaddrs_len; ++ } + addrp = addrs.bulkaddrs_val; + for (i = 0; i < nentries; i++, addrp++) { + if ((*addrp & 0xff000000) == 0xff000000) +@@ -789,6 +798,9 @@ handleit(struct cmd_syndesc *as, void *arock) + printf("VL_GetAddrs returned code = %d\n", code); + continue; + } ++ if (addrs.bulkaddrs_len < nentries) { ++ nentries = addrs.bulkaddrs_len; ++ } + addrp = addrs.bulkaddrs_val; + for (i = 0; i < nentries; i++, addrp++) { + if ((*addrp & 0xff000000) == 0xff000000) { +@@ -814,6 +826,9 @@ handleit(struct cmd_syndesc *as, void *arock) + printf("VL_GetAddrsU returned code = %d\n", code); + continue; + } ++ if (mhaddrs.bulkaddrs_len < mhnentries) { ++ mhnentries = mhaddrs.bulkaddrs_len; ++ } + printf + (" [%d]: uuid[%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x]\n addrunique=%d, ip address(es):\n", + attrs.index, uuid.time_low, uuid.time_mid, +@@ -864,6 +879,9 @@ handleit(struct cmd_syndesc *as, void *arock) + printf("VL_GetAddrs returned code = %d\n", code); + continue; + } ++ if (addrs1.bulkaddrs_len < nentries1) { ++ nentries1 = addrs1.bulkaddrs_len; ++ } + addrp1 = addrs1.bulkaddrs_val; + for (i = 0; i < nentries1; i++, addrp1++) { + if ((*addrp1 & 0xff000000) != 0xff000000) { +@@ -887,6 +905,9 @@ handleit(struct cmd_syndesc *as, void *arock) + printf("VL_GetAddrsU returned code = %d\n", code); + break; + } ++ if (addrs2.bulkaddrs_len < nentries2) { ++ nentries2 = addrs2.bulkaddrs_len; ++ } + + addrp2 = addrs2.bulkaddrs_val; + for (j = 0; j < nentries2; j++) { +diff --git a/src/volser/vsprocs.c b/src/volser/vsprocs.c +index b24ec77f74..e20749fb81 100644 +--- a/src/volser/vsprocs.c ++++ b/src/volser/vsprocs.c +@@ -2865,6 +2865,18 @@ UV_BackupVolume(afs_uint32 aserver, afs_int32 apart, afs_uint32 avolid) + return error; + } + ++static int ++ListOneVolume(struct rx_connection *aconn, afs_int32 apart, afs_uint32 avolid, ++ volEntries *entries) ++{ ++ afs_int32 code; ++ code = AFSVolListOneVolume(aconn, apart, avolid, entries); ++ if (code == 0 && entries->volEntries_len != 1) { ++ code = VOLSERFAILEDOP; ++ } ++ return code; ++} ++ + /* Make a new clone of volume on and + * using volume ID , or a new ID allocated from the VLDB. + * The new volume is named by , or by appending ".clone" to +@@ -2892,7 +2904,7 @@ UV_CloneVolume(afs_uint32 aserver, afs_int32 apart, afs_uint32 avolid, + if (!aname) { + volumeInfo.volEntries_val = (volintInfo *) 0; + volumeInfo.volEntries_len = 0; +- code = AFSVolListOneVolume(aconn, apart, avolid, &volumeInfo); ++ code = ListOneVolume(aconn, apart, avolid, &volumeInfo); + if (code) { + fprintf(stderr, "Could not get info for volume %lu\n", + (unsigned long)avolid); +@@ -3620,9 +3632,8 @@ UV_ReleaseVolume(afs_uint32 afromvol, afs_uint32 afromserver, + } + volumeInfo.volEntries_val = NULL; + volumeInfo.volEntries_len = 0; +- code = AFSVolListOneVolume(conn, entry.serverPartition[vldbindex], +- entry.volumeId[ROVOL], +- &volumeInfo); ++ code = ListOneVolume(conn, entry.serverPartition[vldbindex], ++ entry.volumeId[ROVOL], &volumeInfo); + if (code) { + fprintf(STDERR, "Could not fetch information about RO vol %lu from server %s\n", + (unsigned long)entry.volumeId[ROVOL], +@@ -3650,8 +3661,7 @@ UV_ReleaseVolume(afs_uint32 afromvol, afs_uint32 afromserver, + volEntries volumeInfo; + volumeInfo.volEntries_val = NULL; + volumeInfo.volEntries_len = 0; +- code = AFSVolListOneVolume(fromconn, afrompart, afromvol, +- &volumeInfo); ++ code = ListOneVolume(fromconn, afrompart, afromvol, &volumeInfo); + if (code) { + fprintf(STDERR, "Could not fetch information about RW vol %lu from server %s\n", + (unsigned long)afromvol, +@@ -3993,6 +4003,10 @@ UV_ReleaseVolume(afs_uint32 afromvol, afs_uint32 afromserver, + nservers = 1; + } + ++ if (code == 0 && results.manyResults_len != tr.manyDests_len) { ++ code = VOLSERFAILEDOP; ++ } ++ + if (code) { + PrintError("Release failed: ", code); + } else { +@@ -5503,7 +5517,7 @@ UV_ListOneVolume(afs_uint32 aserver, afs_int32 apart, afs_uint32 volid, + volumeInfo.volEntries_len = 0; + + aconn = UV_Bind(aserver, AFSCONF_VOLUMEPORT); +- code = AFSVolListOneVolume(aconn, apart, volid, &volumeInfo); ++ code = ListOneVolume(aconn, apart, volid, &volumeInfo); + if (code) { + fprintf(STDERR, + "Could not fetch the information about volume %lu from the server\n", +@@ -5570,6 +5584,9 @@ UV_XListOneVolume(afs_uint32 a_serverID, afs_int32 a_partID, afs_uint32 a_volID, + */ + rxConnP = UV_Bind(a_serverID, AFSCONF_VOLUMEPORT); + code = AFSVolXListOneVolume(rxConnP, a_partID, a_volID, &volumeXInfo); ++ if (code == 0 && volumeXInfo.volXEntries_len != 1) { ++ code = VOLSERFAILEDOP; ++ } + if (code) + fprintf(STDERR, + "[UV_XListOneVolume] Couldn't fetch the volume information\n"); +@@ -6191,9 +6208,7 @@ UV_SyncVolume(afs_uint32 aserver, afs_int32 apart, char *avolname, int flags) + /* If a volume ID were given, search for it on each partition */ + if ((volumeid = atol(avolname))) { + for (j = 0; j < pcnt; j++) { +- code = +- AFSVolListOneVolume(aconn, PartList.partId[j], volumeid, +- &volumeInfo); ++ code = ListOneVolume(aconn, PartList.partId[j], volumeid, &volumeInfo); + if (code) { + if (code != ENODEV) { + fprintf(STDERR, "Could not query server\n"); +@@ -6231,9 +6246,8 @@ UV_SyncVolume(afs_uint32 aserver, afs_int32 apart, char *avolname, int flags) + for (k = 0; k < pcnt; k++) { /* For each partition */ + volumeInfo.volEntries_val = (volintInfo *) 0; + volumeInfo.volEntries_len = 0; +- code = +- AFSVolListOneVolume(aconn, PartList.partId[k], +- vldbentry.volumeId[j], &volumeInfo); ++ code = ListOneVolume(aconn, PartList.partId[k], ++ vldbentry.volumeId[j], &volumeInfo); + if (code) { + if (code != ENODEV) { + fprintf(STDERR, "Could not query server\n"); +@@ -6485,7 +6499,7 @@ VolumeExists(afs_uint32 server, afs_int32 partition, afs_uint32 volumeid) + if (conn) { + volumeInfo.volEntries_val = (volintInfo *) 0; + volumeInfo.volEntries_len = 0; +- code = AFSVolListOneVolume(conn, partition, volumeid, &volumeInfo); ++ code = ListOneVolume(conn, partition, volumeid, &volumeInfo); + if (volumeInfo.volEntries_val) + free(volumeInfo.volEntries_val); + if (code == VOLSERILLEGAL_PARTITION) +diff --git a/src/volser/vsutils.c b/src/volser/vsutils.c +index e5d0beb7ec..fca2283e7a 100644 +--- a/src/volser/vsutils.c ++++ b/src/volser/vsutils.c +@@ -377,6 +377,9 @@ VLDB_IsSameAddrs(afs_uint32 serv1, afs_uint32 serv2, afs_int32 *errorp) + } + + code = 0; ++ if (addrs.bulkaddrs_len < nentries) { ++ nentries = addrs.bulkaddrs_len; ++ } + if (nentries > GETADDRUCACHESIZE) + nentries = GETADDRUCACHESIZE; /* safety check; should not happen */ + if (++cacheip_index >= GETADDRUCACHESIZE) +-- +2.45.2 + + +From a82212ab20f0635a40c52648a52a1e9eaccc4937 Mon Sep 17 00:00:00 2001 +From: Andrew Deason +Date: Thu, 15 Oct 2020 20:30:14 -0500 +Subject: [PATCH 6/9] OPENAFS-SA-2024-003: xdr: Ensure correct string length in + xdr_string + +CVE-2024-10397 + +Currently, if a caller calls an RPC with a string output argument, +like so: + +{ + char *str = NULL; + code = RXAFS_SomeCall(&str); + /* do something with 'str' */ + xdr_free((xdrproc_t) xdr_string, &str); +} + +Normally, xdr_free causes xdr_string to call osi_free, specifying the +same size that we allocated for the string. However, since we only +have a char*, the amount of space allocated for the string is not +recorded separately, and so xdr_string calculates the size of the +buffer to free by using strlen(). + +This works for well-formed strings, but if we fail to decode the +payload of the string, or if our peer gave us a string with a NUL byte +in the middle of it, then strlen() may be significantly less than the +actual allocated size. And so in this case, the size given to osi_free +will be wrong. + +The size given to osi_free is ignored in userspace, and for KERNEL on +many platforms like Linux and DARWIN. However, it is notably not +ignored for KERNEL on Solaris and some other less supported platforms +(HPUX, Irix, NetBSD). At least on Solaris, an incorrect size given to +osi_free can cause a system panic or possibly memory corruption. + +To avoid this, change xdr_string during XDR_DECODE to make sure that +strlen() of the string always reflects the allocated size. If we fail +to decode the string's payload, replace the payload with non-NUL bytes +(fill it with 'z', an arbitrary choice). And if we do successfully +decode the payload, check if the strlen() is wrong (that is, if the +payload contains NUL '\0' bytes), and fail if so, also filling the +payload with 'z'. This is only strictly needed in KERNEL on certain +platforms, but do it everywhere so our behavior is consistent. + +FIXES 135043 + +Reviewed-on: https://gerrit.openafs.org/15922 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +(cherry picked from commit 7d0675e6c6a2f3200a3884fbe46b3ef8ef9ffd24) + +Change-Id: Ieb8827474a7458ce80176b14ce87f3402aed7a86 +Reviewed-on: https://gerrit.openafs.org/15944 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +--- + src/rx/xdr.c | 21 ++++++++++++++++++++- + 1 file changed, 20 insertions(+), 1 deletion(-) + +diff --git a/src/rx/xdr.c b/src/rx/xdr.c +index ef31d455ac..6826a28715 100644 +--- a/src/rx/xdr.c ++++ b/src/rx/xdr.c +@@ -573,7 +573,26 @@ xdr_string(XDR * xdrs, char **cpp, u_int maxsize) + return (FALSE); + } + sp[size] = 0; +- AFS_FALLTHROUGH; ++ ++ /* Get the actual string. */ ++ if (!xdr_opaque(xdrs, sp, size)) { ++ /* Make sure strlen(sp) == size, so we can calculate the correct ++ * size for osi_free when freeing the string. */ ++ memset(sp, 'z', size); ++ return FALSE; ++ } ++ ++ /* ++ * If the string contains a '\0' character, the string is invalid. ++ * Don't allow this, because this makes it impossible for us to pass ++ * the correct size to osi_free later on, when freeing the string. ++ */ ++ if (strlen(sp) != size) { ++ /* Make sure strlen(sp) == size. */ ++ memset(sp, 'z', size); ++ return FALSE; ++ } ++ return TRUE; + + case XDR_ENCODE: + return (xdr_opaque(xdrs, sp, size)); +-- +2.45.2 + + +From 25ad3931d5c03ead625a96e6b626febeb3e20453 Mon Sep 17 00:00:00 2001 +From: Andrew Deason +Date: Fri, 16 Oct 2020 10:52:03 -0500 +Subject: [PATCH 7/9] OPENAFS-SA-2024-003: Run xdr_free for retried RPCs + +CVE-2024-10397 + +A few areas of code retry the same RPC, like so: + + do { + code = VL_SomeRPC(rxconn, &array_out); + } while (some_condition); + xdr_free((xdrproc_t) xdr_foo, &array_out); + +Or try a different version/variant of an RPC (e.g. +VLDB_ListAttributesN2 -> VLDB_ListAttributes). + +If the first RPC call causes the output array to be allocated with +length N, then the subsequent RPC calls may fail if the server +responds with an array larger than N. + +Furthermore, if the subsequent call responds with an array smaller +than N, then when we xdr_free the array, our length will be smaller +than the actual number of allocated elements. That results in two +potential issues: + +- We'll fail to free the elements at the end of the array. This is + only a problem if each element in the array also uses + dynamically-allocated memory (e.g. each element contains a string or + another array). Fortunately, there are only a few such structures in + any of our RPC-L definitions: SysNameList and CredInfos. And neither + of those are used in such a retry loop, so this isn't a problem. + +- We'll give the wrong length to osi_free when freeing the array + itself. This only matters for KERNEL, and only on some platforms + (such as Solaris), since the length given to osi_free is ignored + everywhere else. + +To avoid these possible issues, change the relevant retry loops to +free our xdr-allocated arrays on every iteration of the loop, like +this: + + do { + xdr_free((xdrproc_t) xdr_foo, &array_out); + code = VL_SomeRPC(rxconn, &array_out); + } while (some_condition); + xdr_free((xdrproc_t) xdr_foo, &array_out); + +Or like this: + + do { + code = VL_SomeRPC(rxconn, &array_out); + xdr_free((xdrproc_t) xdr_foo, &array_out); + } while (some_condition); + +FIXES 135043 + +Reviewed-on: https://gerrit.openafs.org/15923 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +(cherry picked from commit 1f5e1ef9e35f6b5e8693c91199c976d5e030c0d0) + +Change-Id: I77ce3a904d502784cbf356e113972dfab838256e +Reviewed-on: https://gerrit.openafs.org/15945 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +--- + src/WINNT/afsd/cm_getaddrs.c | 1 + + src/afs/afs_pioctl.c | 1 + + src/afs/afs_volume.c | 1 + + src/ptserver/pts.c | 1 + + src/ptserver/testpt.c | 1 + + src/volser/vos.c | 1 + + 6 files changed, 6 insertions(+) + +diff --git a/src/afs/afs_pioctl.c b/src/afs/afs_pioctl.c +index 5337986c4e..faafdd3d2d 100644 +--- a/src/afs/afs_pioctl.c ++++ b/src/afs/afs_pioctl.c +@@ -1596,6 +1596,7 @@ DECL_PIOCTL(PGetAcl) + if (tconn) { + XSTATS_START_TIME(AFS_STATS_FS_RPCIDX_FETCHACL); + RX_AFS_GUNLOCK(); ++ xdr_free((xdrproc_t) xdr_AFSOpaque, &acl); + code = RXAFS_FetchACL(rxconn, &Fid, &acl, &OutStatus, &tsync); + RX_AFS_GLOCK(); + XSTATS_END_TIME; +diff --git a/src/afs/afs_volume.c b/src/afs/afs_volume.c +index 0600ccbfaf..42f1ba9d91 100644 +--- a/src/afs/afs_volume.c ++++ b/src/afs/afs_volume.c +@@ -1205,6 +1205,7 @@ LockAndInstallUVolumeEntry(struct volume *av, struct uvldbentry *ve, int acell, + 0, &rxconn); + if (tconn) { + RX_AFS_GUNLOCK(); ++ xdr_free((xdrproc_t) xdr_bulkaddrs, &addrs); + code = + VL_GetAddrsU(rxconn, &attrs, &uuid, &unique, + &nentries, &addrs); +diff --git a/src/ptserver/pts.c b/src/ptserver/pts.c +index c97307bdf1..05508a70df 100644 +--- a/src/ptserver/pts.c ++++ b/src/ptserver/pts.c +@@ -681,6 +681,7 @@ CheckEntry(struct cmd_syndesc *as, void *arock) + + lids.idlist_val[0] = aentry.owner; + lids.idlist_val[1] = aentry.creator; ++ xdr_free((xdrproc_t) xdr_namelist, &lnames); + code = pr_IdToName(&lids, &lnames); + if (code) { + rcode = code; +diff --git a/src/ptserver/testpt.c b/src/ptserver/testpt.c +index c359f8f465..50f0029f6f 100644 +--- a/src/ptserver/testpt.c ++++ b/src/ptserver/testpt.c +@@ -112,6 +112,7 @@ ListUsedIds(struct cmd_syndesc *as, void *arock) + startId++; + } + lids.idlist_len = i; ++ xdr_free((xdrproc_t) xdr_namelist, &lnames); + code = pr_IdToName(&lids, &lnames); + if (code) { + afs_com_err(whoami, code, "converting id to name"); +diff --git a/src/volser/vos.c b/src/volser/vos.c +index 007bfe5064..6efc1c73fd 100644 +--- a/src/volser/vos.c ++++ b/src/volser/vos.c +@@ -4520,6 +4520,7 @@ ListVLDB(struct cmd_syndesc *as, void *arock) + &arrayEntries, &nextindex); + if (vcode == RXGEN_OPCODE) { + /* Vlserver not running with ListAttributesN2. Fall back */ ++ xdr_free((xdrproc_t) xdr_nbulkentries, &arrayEntries); + vcode = + VLDB_ListAttributes(&attributes, ¢ries, &arrayEntries); + nextindex = -1; +-- +2.45.2 + + +From 4871f8ad2775e97bb85ff7efc33a4ad8d3f6d9d1 Mon Sep 17 00:00:00 2001 +From: Andrew Deason +Date: Fri, 16 Oct 2020 10:55:15 -0500 +Subject: [PATCH 8/9] OPENAFS-SA-2024-003: sys: Don't over-copy RMTSYS_Pioctl + output data + +CVE-2024-10397 + +Here, 'OutData' only has OutData.rmtbulk_len bytes in it. We know that +OutData.rmtbulk_len is at most data->out_size, but it could be +smaller. So, only copy OutData.rmtbulk_len bytes, not data->out_size, +since data->out_size could be more than the number of bytes we have +allocated in OutData. + +FIXES 135043 + +Reviewed-on: https://gerrit.openafs.org/15924 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +(cherry picked from commit f31a79d749abc8e64a8d9ac748bb2b5457875099) + +Change-Id: Ic05751d05c7c8862770188131110cc602c9b93b7 +Reviewed-on: https://gerrit.openafs.org/15946 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +--- + src/sys/rmtsysc.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/sys/rmtsysc.c b/src/sys/rmtsysc.c +index cf16afcea0..0a41489b89 100644 +--- a/src/sys/rmtsysc.c ++++ b/src/sys/rmtsysc.c +@@ -260,7 +260,7 @@ pioctl(char *path, afs_int32 cmd, struct ViceIoctl *data, afs_int32 follow) + errno = EINVAL; + errorcode = -1; + } else { +- memcpy(data->out, OutData.rmtbulk_val, data->out_size); ++ memcpy(data->out, OutData.rmtbulk_val, OutData.rmtbulk_len); + outparam_conversion(cmd, data->out, 1); + } + } +-- +2.45.2 + + +From 37e585f0841803cdf3a1f99770034890ba162d7c Mon Sep 17 00:00:00 2001 +From: Andrew Deason +Date: Thu, 15 Oct 2020 21:07:17 -0500 +Subject: [PATCH 9/9] OPENAFS-SA-2024-003: xdr: Initialize memory for INOUT + args + +CVE-2024-10397 + +Currently, there are a few callers of RPCs that specify some data for +an INOUT parameter, but do not initialize the memory for that data. +This can result in the uninitialized memory being sent to the peer +when the argument is processed as an IN argument. Simply clear the +relevant data before running the RPC to avoid this. + +The relevant RPCs and arguments are: + +- For RMTSYS_Pioctl, the 'OutData' argument. + +- For BUDB_GetVolumes, the 'volumes' argument. +-- via DBLookupByVolume -> bcdb_LookupVolume -> ubik_BUDB_GetVolumes +-- and via bc_Restorer -> bcdb_FindVolumes -> ubik_BUDB_GetVolumes + +- For KAA_Authenticate_old / KAA_Authenticate, this can happen with + the 'answer' argument in ka_Authenticate if KAA_AuthenticateV2 or + KAA_Authenticate return RXGEN_OPCODE, but the server manages to + populate oanswer.SeqLen with non-zero. + +For all of these, make sure the memory is blanked before running the +relevant RPC. For ka_Authenticate, reset oanswer.SeqLen to 0 to avoid +sending any data, but still blank 'answer' and 'answer_old' just to be +safe. + +FIXES 135043 + +Reviewed-on: https://gerrit.openafs.org/15925 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +(cherry picked from commit c4e28c2afe743aa323be57ef3b0faec13027e678) + +Change-Id: If44320c1efde98c53eed88099cd978ef89f4c0d8 +Reviewed-on: https://gerrit.openafs.org/15947 +Reviewed-by: Benjamin Kaduk +Tested-by: Benjamin Kaduk +--- + src/afs/afs_pag_call.c | 2 ++ + src/bucoord/commands.c | 2 ++ + src/bucoord/restore.c | 2 +- + src/kauth/authclient.c | 4 ++++ + src/sys/rmtsysc.c | 2 +- + 5 files changed, 10 insertions(+), 2 deletions(-) + +diff --git a/src/afs/afs_pag_call.c b/src/afs/afs_pag_call.c +index 610cfb3a84..679ef3a701 100644 +--- a/src/afs/afs_pag_call.c ++++ b/src/afs/afs_pag_call.c +@@ -461,6 +461,8 @@ afs_syscall_pioctl(char *path, unsigned int com, caddr_t cmarg, int follow) + goto out_idata; + } + ++ memset(odata.rmtbulk_val, 0, out_size); ++ + AFS_GUNLOCK(); + code = RMTSYS_Pioctl(rmtsys_conn, &ccred, abspath, com, follow, + &idata, &odata, &err); +diff --git a/src/bucoord/commands.c b/src/bucoord/commands.c +index 10f088a478..dc7847fd14 100644 +--- a/src/bucoord/commands.c ++++ b/src/bucoord/commands.c +@@ -2725,6 +2725,8 @@ DBLookupByVolume(char *volumeName) + char vname[BU_MAXNAMELEN]; + char ds[50]; + ++ memset(volumeEntry, 0, sizeof(volumeEntry)); ++ + for (pass = 0; pass < 2; pass++) { + /*p */ + /* On second pass, search for backup volume */ +diff --git a/src/bucoord/restore.c b/src/bucoord/restore.c +index b73a9351f3..b7fb1d4b3f 100644 +--- a/src/bucoord/restore.c ++++ b/src/bucoord/restore.c +@@ -191,7 +191,7 @@ bc_Restorer(afs_int32 aindex) + serverAll = HOSTADDR(&dumpTaskPtr->destServer); + partitionAll = dumpTaskPtr->destPartition; + +- volumeEntries = malloc(MAXTAPESATONCE * sizeof(struct budb_volumeEntry)); ++ volumeEntries = calloc(MAXTAPESATONCE, sizeof(struct budb_volumeEntry)); + if (!volumeEntries) { + afs_com_err(whoami, BC_NOMEM, NULL); + ERROR(BC_NOMEM); +diff --git a/src/kauth/authclient.c b/src/kauth/authclient.c +index 4db0cbc134..c64f330edf 100644 +--- a/src/kauth/authclient.c ++++ b/src/kauth/authclient.c +@@ -525,12 +525,15 @@ ka_Authenticate(char *name, char *instance, char *cell, struct ubik_client * con + oanswer.MaxSeqLen = sizeof(answer); + oanswer.SeqLen = 0; + oanswer.SeqBody = (char *)&answer; ++ memset(&answer, 0, sizeof(answer)); ++ memset(&answer_old, 0, sizeof(answer_old)); + + version = 2; + code = + kawrap_ubik_Call(KAA_AuthenticateV2, conn, 0, name, instance, + (void*)(uintptr_t)start, (void*)(uintptr_t)end, &arequest, &oanswer, 0, 0); + if (code == RXGEN_OPCODE) { ++ oanswer.SeqLen = 0; + oanswer.MaxSeqLen = sizeof(answer); + oanswer.SeqBody = (char *)&answer; + version = 1; +@@ -538,6 +541,7 @@ ka_Authenticate(char *name, char *instance, char *cell, struct ubik_client * con + ubik_KAA_Authenticate(conn, 0, name, instance, start, end, + &arequest, &oanswer); + if (code == RXGEN_OPCODE) { ++ oanswer.SeqLen = 0; + oanswer.MaxSeqLen = sizeof(answer_old); + oanswer.SeqBody = (char *)&answer_old; + version = 0; +diff --git a/src/sys/rmtsysc.c b/src/sys/rmtsysc.c +index 0a41489b89..6881234220 100644 +--- a/src/sys/rmtsysc.c ++++ b/src/sys/rmtsysc.c +@@ -214,7 +214,7 @@ pioctl(char *path, afs_int32 cmd, struct ViceIoctl *data, afs_int32 follow) + inparam_conversion(cmd, InData.rmtbulk_val, 0); + + OutData.rmtbulk_len = MAXBUFFERLEN * sizeof(*OutData.rmtbulk_val); +- OutData.rmtbulk_val = malloc(OutData.rmtbulk_len); ++ OutData.rmtbulk_val = calloc(1, OutData.rmtbulk_len); + if (!OutData.rmtbulk_val) { + free(inbuffer); + return -1; +-- +2.45.2 + diff -Nru openafs-1.8.9/debian/patches/series openafs-1.8.9/debian/patches/series --- openafs-1.8.9/debian/patches/series 2022-12-22 18:34:01.000000000 +0000 +++ openafs-1.8.9/debian/patches/series 2024-12-25 20:19:02.000000000 +0000 @@ -2,3 +2,7 @@ 0005-tests-skip-vos-tests-when-a-vlserver-is-already-runn.patch 0007-Temporarily-disable-flaky-test.patch 0004-Disable-rx-perf-test.patch +Properly-type-afs_osi_suser-cred-arg.patch +openafs-sa-2024-001-stable18.patch +openafs-sa-2024-002-stable18.patch +openafs-sa-2024-003-stable18.patch