From 00a1b266af51a828a022c23e7bb006a39740eaad Mon Sep 17 00:00:00 2001
From: Andrew Deason <adeason@sinenomine.net>
Date: Thu, 13 Jun 2024 15:28:38 -0500
Subject: [PATCH 1/8] 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

Change-Id: If42e2cc983903cff9766e1bab487142d4d493a17
Reviewed-on: https://gerrit.openafs.org/15918
Reviewed-by: Benjamin Kaduk <kaduk@mit.edu>
Tested-by: Benjamin Kaduk <kaduk@mit.edu>
---
 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/WINNT/afsd/cm_ioctl.c b/src/WINNT/afsd/cm_ioctl.c
index e8c31b5ac6..015ed14a28 100644
--- a/src/WINNT/afsd/cm_ioctl.c
+++ b/src/WINNT/afsd/cm_ioctl.c
@@ -764,9 +764,6 @@ cm_IoctlGetVolumeStatus(struct cm_ioctl *ioctlp, struct cm_user *userp, cm_scach
     cm_conn_t *connp;
     AFSFetchVolumeStatus volStat;
     char *cp;
-    char *Name;
-    char *OfflineMsg;
-    char *MOTD;
     struct rx_connection * rxconnp;
 
 #ifdef AFS_FREELANCE_CLIENT
@@ -785,19 +782,23 @@ cm_IoctlGetVolumeStatus(struct cm_ioctl *ioctlp, struct cm_user *userp, cm_scach
     {
         cm_fid_t    vfid;
         cm_scache_t *vscp;
+	char *Name = NULL;
+	char *OfflineMsg = NULL;
+	char *MOTD = NULL;
 
         cm_SetFid(&vfid, scp->fid.cell, scp->fid.volume, 1, 1);
         code = cm_GetSCache(&vfid, NULL, &vscp, userp, reqp);
         if (code)
             return code;
 
-        Name = volName;
-        OfflineMsg = offLineMsg;
-        MOTD = motd;
         do {
             code = cm_ConnFromFID(&vfid, userp, reqp, &connp);
             if (code) continue;
 
+	    xdr_free((xdrproc_t) xdr_string, &Name);
+	    xdr_free((xdrproc_t) xdr_string, &OfflineMsg);
+	    xdr_free((xdrproc_t) xdr_string, &MOTD);
+
             rxconnp = cm_GetRxConn(connp);
             code = RXAFS_GetVolumeStatus(rxconnp, vfid.volume,
                                          &volStat, &Name, &OfflineMsg, &MOTD);
@@ -807,6 +808,19 @@ cm_IoctlGetVolumeStatus(struct cm_ioctl *ioctlp, struct cm_user *userp, cm_scach
         code = cm_MapRPCError(code, reqp);
 
         cm_ReleaseSCache(vscp);
+
+	strncpy(volName, Name, sizeof(volName));
+	volName[sizeof(volName) - 1] = '\0';
+
+	strncpy(offLineMsg, OfflineMsg, sizeof(offLineMsg));
+	offLineMsg[sizeof(offLineMsg) - 1] = '\0';
+
+	strncpy(motd, MOTD, sizeof(motd));
+	motd[sizeof(motd) - 1] = '\0';
+
+	xdr_free((xdrproc_t) xdr_string, &Name);
+	xdr_free((xdrproc_t) xdr_string, &OfflineMsg);
+	xdr_free((xdrproc_t) xdr_string, &MOTD);
     }
 
     if (code)
diff --git a/src/WINNT/afsd/cm_vnodeops.c b/src/WINNT/afsd/cm_vnodeops.c
index 074a970450..226bf7af00 100644
--- a/src/WINNT/afsd/cm_vnodeops.c
+++ b/src/WINNT/afsd/cm_vnodeops.c
@@ -2743,12 +2743,6 @@ cm_IsSpaceAvailable(cm_fid_t * fidp, osi_hyper_t *sizep, cm_user_t *userp, cm_re
     AFSFetchVolumeStatus volStat;
     cm_volume_t *volp = NULL;
     afs_uint32   volType;
-    char *Name;
-    char *OfflineMsg;
-    char *MOTD;
-    char volName[32]="(unknown)";
-    char offLineMsg[256]="server temporarily inaccessible";
-    char motd[256]="server temporarily inaccessible";
     osi_hyper_t freespace;
     cm_fid_t    vfid;
     cm_scache_t *vscp;
@@ -2778,11 +2772,11 @@ cm_IsSpaceAvailable(cm_fid_t * fidp, osi_hyper_t *sizep, cm_user_t *userp, cm_re
                           CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
         lock_ReleaseWrite(&vscp->rw);
         if (code == 0) {
-            Name = volName;
-            OfflineMsg = offLineMsg;
-            MOTD = motd;
-
             do {
+		char *Name = NULL;
+		char *OfflineMsg = NULL;
+		char *MOTD = NULL;
+
                 code = cm_ConnFromFID(&vfid, userp, reqp, &connp);
                 if (code) continue;
 
@@ -2791,6 +2785,10 @@ cm_IsSpaceAvailable(cm_fid_t * fidp, osi_hyper_t *sizep, cm_user_t *userp, cm_re
                                              &volStat, &Name, &OfflineMsg, &MOTD);
                 rx_PutConnection(rxconnp);
 
+		xdr_free((xdrproc_t) xdr_string, &Name);
+		xdr_free((xdrproc_t) xdr_string, &OfflineMsg);
+		xdr_free((xdrproc_t) xdr_string, &MOTD);
+
             } while (cm_Analyze(connp, userp, reqp, &vfid, NULL, 0, NULL, NULL, NULL, NULL, code));
             code = cm_MapRPCError(code, reqp);
         }
diff --git a/src/WINNT/afsd/cm_volume.c b/src/WINNT/afsd/cm_volume.c
index 017c50f597..5cd262857c 100644
--- a/src/WINNT/afsd/cm_volume.c
+++ b/src/WINNT/afsd/cm_volume.c
@@ -1310,24 +1310,14 @@ cm_CheckOfflineVolumeState(cm_volume_t *volp, cm_vol_state_t *statep, afs_uint32
     cm_conn_t *connp;
     long code;
     AFSFetchVolumeStatus volStat;
-    char *Name;
-    char *OfflineMsg;
-    char *MOTD;
     cm_req_t req;
     struct rx_connection * rxconnp;
-    char volName[32];
     afs_uint32 volType;
-    char offLineMsg[256];
-    char motd[256];
     long alldown, alldeleted;
     cm_serverRef_t *serversp;
     cm_fid_t vfid;
     cm_scache_t *vscp = NULL;
 
-    Name = volName;
-    OfflineMsg = offLineMsg;
-    MOTD = motd;
-
     volType = cm_VolumeType(volp, volID);
 
     if (statep->ID != 0 && (!volID || volID == statep->ID)) {
@@ -1373,6 +1363,10 @@ cm_CheckOfflineVolumeState(cm_volume_t *volp, cm_vol_state_t *statep, afs_uint32
                 code = cm_GetSCache(&vfid, NULL, &vscp, cm_rootUserp, &req);
                 if (code = 0) {
 		    do {
+			char *Name = NULL;
+			char *OfflineMsg = NULL;
+			char *MOTD = NULL;
+
 			code = cm_ConnFromVolume(volp, statep->ID, cm_rootUserp, &req, &connp);
 			if (code)
 			   continue;
@@ -1381,6 +1375,11 @@ cm_CheckOfflineVolumeState(cm_volume_t *volp, cm_vol_state_t *statep, afs_uint32
 			code = RXAFS_GetVolumeStatus(rxconnp, statep->ID,
 						     &volStat, &Name, &OfflineMsg, &MOTD);
 			rx_PutConnection(rxconnp);
+
+			xdr_free((xdrproc_t) xdr_string, &Name);
+			xdr_free((xdrproc_t) xdr_string, &OfflineMsg);
+			xdr_free((xdrproc_t) xdr_string, &MOTD);
+
 		    } while (cm_Analyze(connp, cm_rootUserp, &req, &vfid, NULL, 0, NULL, NULL, NULL, NULL, code));
 		    code = cm_MapRPCError(code, &req);
 
diff --git a/src/WINNT/afsrdr/user/RDRFunction.c b/src/WINNT/afsrdr/user/RDRFunction.c
index cffb9b58c6..db767cbb8a 100644
--- a/src/WINNT/afsrdr/user/RDRFunction.c
+++ b/src/WINNT/afsrdr/user/RDRFunction.c
@@ -5820,14 +5820,8 @@ RDR_GetVolumeInfo( IN cm_user_t     *userp,
     DWORD       status;
     FILETIME ft = {0x832cf000, 0x01abfcc4}; /* October 1, 1982 00:00:00 +0600 */
 
-    char volName[32]="(unknown)";
-    char offLineMsg[256]="server temporarily inaccessible";
-    char motd[256]="server temporarily inaccessible";
     cm_conn_t *connp;
     AFSFetchVolumeStatus volStat;
-    char *Name;
-    char *OfflineMsg;
-    char *MOTD;
     struct rx_connection * rxconnp;
     int scp_locked = 0;
 
@@ -5928,13 +5922,14 @@ RDR_GetVolumeInfo( IN cm_user_t     *userp,
         
         if (code == -1)
         {
-	    Name = volName;
-	    OfflineMsg = offLineMsg;
-	    MOTD = motd;
 	    lock_ReleaseWrite(&scp->rw);
 	    scp_locked = 0;
 
 	    do {
+		char *Name = NULL;
+		char *OfflineMsg = NULL;
+		char *MOTD = NULL;
+
 		code = cm_ConnFromFID(&scp->fid, userp, &req, &connp);
 		if (code) continue;
 
@@ -5943,6 +5938,10 @@ RDR_GetVolumeInfo( IN cm_user_t     *userp,
 					     &volStat, &Name, &OfflineMsg, &MOTD);
 		rx_PutConnection(rxconnp);
 
+		xdr_free((xdrproc_t) xdr_string, &Name);
+		xdr_free((xdrproc_t) xdr_string, &OfflineMsg);
+		xdr_free((xdrproc_t) xdr_string, &MOTD);
+
 	    } while (cm_Analyze(connp, userp, &req, &scp->fid, NULL, 0, NULL, NULL, NULL, NULL, code));
 	    code = cm_MapRPCError(code, &req);
 
@@ -6056,14 +6055,8 @@ RDR_GetVolumeSizeInfo( IN cm_user_t     *userp,
     cm_req_t    req;
     DWORD       status;
 
-    char volName[32]="(unknown)";
-    char offLineMsg[256]="server temporarily inaccessible";
-    char motd[256]="server temporarily inaccessible";
     cm_conn_t *connp;
     AFSFetchVolumeStatus volStat;
-    char *Name;
-    char *OfflineMsg;
-    char *MOTD;
     struct rx_connection * rxconnp;
     int scp_locked = 0;
 
@@ -6141,13 +6134,14 @@ RDR_GetVolumeSizeInfo( IN cm_user_t     *userp,
         
         if (code == -1)
         {
-	    Name = volName;
-	    OfflineMsg = offLineMsg;
-	    MOTD = motd;
 	    lock_ReleaseWrite(&scp->rw);
 	    scp_locked = 0;
 
 	    do {
+		char *Name = NULL;
+		char *OfflineMsg = NULL;
+		char *MOTD = NULL;
+
 		code = cm_ConnFromFID(&scp->fid, userp, &req, &connp);
 		if (code) continue;
 
@@ -6156,6 +6150,10 @@ RDR_GetVolumeSizeInfo( IN cm_user_t     *userp,
 					     &volStat, &Name, &OfflineMsg, &MOTD);
 		rx_PutConnection(rxconnp);
 
+		xdr_free((xdrproc_t) xdr_string, &Name);
+		xdr_free((xdrproc_t) xdr_string, &OfflineMsg);
+		xdr_free((xdrproc_t) xdr_string, &MOTD);
+
 	    } while (cm_Analyze(connp, userp, &req, &scp->fid, NULL, 0, NULL, NULL, NULL, NULL, code));
 	    code = cm_MapRPCError(code, &req);
 
diff --git a/src/afs/afs_pioctl.c b/src/afs/afs_pioctl.c
index f8a4715555..2302a6351d 100644
--- a/src/afs/afs_pioctl.c
+++ b/src/afs/afs_pioctl.c
@@ -1997,32 +1997,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
@@ -2043,8 +2042,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, &notifier);
+			       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 b2b1110ddd9e19670dbc6a3217dc2a74af432f82 Mon Sep 17 00:00:00 2001
From: Andrew Deason <adeason@sinenomine.net>
Date: Thu, 13 Jun 2024 15:30:50 -0500
Subject: [PATCH 2/8] 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

Change-Id: Ieb50aaa5ae9a1bde027999ce1c668e0c99b4d82b
Reviewed-on: https://gerrit.openafs.org/15919
Reviewed-by: Benjamin Kaduk <kaduk@mit.edu>
Tested-by: Benjamin Kaduk <kaduk@mit.edu>
---
 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/WINNT/afsd/cm_ioctl.c b/src/WINNT/afsd/cm_ioctl.c
index 015ed14a28..ffe5f70ae8 100644
--- a/src/WINNT/afsd/cm_ioctl.c
+++ b/src/WINNT/afsd/cm_ioctl.c
@@ -433,7 +433,7 @@ cm_IoctlGetACL(cm_ioctl_t *ioctlp, cm_user_t *userp, cm_scache_t *scp, cm_req_t
         afid.Unique = scp->fid.unique;
         do {
             acl.AFSOpaque_val = ioctlp->outDatap;
-            acl.AFSOpaque_len = 0;
+	    acl.AFSOpaque_len = SMB_IOCTL_MAXDATA - (ioctlp->outDatap - ioctlp->outAllocp);
             code = cm_ConnFromFID(&scp->fid, userp, reqp, &connp);
             if (code)
                 continue;
diff --git a/src/afs/afs_pioctl.c b/src/afs/afs_pioctl.c
index 2302a6351d..0157bdc269 100644
--- a/src/afs/afs_pioctl.c
+++ b/src/afs/afs_pioctl.c
@@ -1593,11 +1593,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);
@@ -1611,7 +1610,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. */
@@ -1622,9 +1621,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 7690992904..27a14d21b6 100644
--- a/src/fsprobe/fsprobe.c
+++ b/src/fsprobe/fsprobe.c
@@ -224,6 +224,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 13413eceed80d106cbed5ffb91c4dfbc8cccf55c Mon Sep 17 00:00:00 2001
From: Andrew Deason <adeason@sinenomine.net>
Date: Sun, 4 Oct 2020 23:04:06 -0500
Subject: [PATCH 3/8] 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

Change-Id: Iaf913e2156af2081c72125ec066d9636086c7d14
Reviewed-on: https://gerrit.openafs.org/15920
Reviewed-by: Benjamin Kaduk <kaduk@mit.edu>
Tested-by: Benjamin Kaduk <kaduk@mit.edu>
---
 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 3353b72c33..53a9449a7c 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 c732715e4ee78ed1e2414c813ae5a4b3574107a0 Mon Sep 17 00:00:00 2001
From: Andrew Deason <adeason@sinenomine.net>
Date: Thu, 15 Oct 2020 23:18:53 -0500
Subject: [PATCH 4/8] 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

Change-Id: Ibdc7837ab09b4765436fc4c0d780e695bba07128
Reviewed-on: https://gerrit.openafs.org/15921
Reviewed-by: Benjamin Kaduk <kaduk@mit.edu>
Tested-by: Benjamin Kaduk <kaduk@mit.edu>
---
 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/WINNT/afsd/cm_getaddrs.c b/src/WINNT/afsd/cm_getaddrs.c
index 73752bc3e8..4db33019d9 100644
--- a/src/WINNT/afsd/cm_getaddrs.c
+++ b/src/WINNT/afsd/cm_getaddrs.c
@@ -260,6 +260,8 @@ cm_GetAddrsU(cm_cell_t *cellp, cm_user_t *userp, cm_req_t *reqp,
 	if (code)
 	    return CM_ERROR_RETRY;
 
+	nentries = min(nentries, addrs.bulkaddrs_len);
+
 	if (nentries == 0) {
 	    xdr_free((xdrproc_t) xdr_bulkaddrs, &addrs);
 	    code = CM_ERROR_INVAL;
diff --git a/src/WINNT/afsd/cm_vnodeops.c b/src/WINNT/afsd/cm_vnodeops.c
index 226bf7af00..64a3dbe65d 100644
--- a/src/WINNT/afsd/cm_vnodeops.c
+++ b/src/WINNT/afsd/cm_vnodeops.c
@@ -2519,6 +2519,13 @@ cm_TryBulkStatRPC(cm_scache_t *dscp, cm_bulkStat_t *bbp, cm_user_t *userp, cm_re
 	    }
             rx_PutConnection(rxconnp);
 
+	    if (code == 0) {
+		if (statStruct.AFSBulkStats_len != filesThisCall ||
+		    callbackStruct.AFSCBs_len != filesThisCall) {
+		    code = RXGEN_CC_UNMARSHAL;
+		}
+	    }
+
             /*
              * If InlineBulk RPC was called and it succeeded,
              * then pull out the return code from the status info
diff --git a/src/afs/afs_volume.c b/src/afs/afs_volume.c
index efbb66e1bf..21c729eb32 100644
--- a/src/afs/afs_volume.c
+++ b/src/afs/afs_volume.c
@@ -1228,6 +1228,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 ab0bc90195..a049881460 100644
--- a/src/bucoord/ubik_db_if.c
+++ b/src/bucoord/ubik_db_if.c
@@ -116,6 +116,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 4798117600..792461418c 100644
--- a/src/libadmin/adminutil/afs_utilAdmin.c
+++ b/src/libadmin/adminutil/afs_utilAdmin.c
@@ -2324,6 +2324,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;
@@ -2335,8 +2338,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);
@@ -2345,12 +2346,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 c1172bce62..10da408e25 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 590b1fefef..213a210b7b 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);
 	afs_dprintf(("GetServerByAddr 0x%x -> uuid %s\n", addr, s.buffer));
 
+	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 6e990576fd..b1dc16eb15 100644
--- a/src/ptserver/ptuser.c
+++ b/src/ptserver/ptuser.c
@@ -551,6 +551,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;
 }
 
@@ -596,6 +599,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 b72473a6d3..b8c1550c57 100644
--- a/src/viced/host.c
+++ b/src/viced/host.c
@@ -30,6 +30,7 @@
 #include <afs/ihandle.h>
 #include <afs/acl.h>
 #include <afs/ptclient.h>
+#include <afs/pterror.h>
 #include <afs/ptuser.h>
 #include <afs/prs_fs.h>
 #include <afs/auth.h>
@@ -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 61a2b60138..d3f98028a8 100644
--- a/src/volser/vsprocs.c
+++ b/src/volser/vsprocs.c
@@ -2850,6 +2850,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 <avolid> on <aserver> and <apart>
  * using volume ID <acloneid>, or a new ID allocated from the VLDB.
  * The new volume is named by <aname>, or by appending ".clone" to
@@ -2877,7 +2889,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);
@@ -3598,9 +3610,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],
@@ -3628,8 +3639,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,
@@ -3971,6 +3981,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 {
@@ -5325,7 +5339,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",
@@ -5392,6 +5406,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");
@@ -6097,9 +6114,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");
@@ -6137,9 +6152,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");
@@ -6391,7 +6405,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 3428d8366d..475d700f6a 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 7d0675e6c6a2f3200a3884fbe46b3ef8ef9ffd24 Mon Sep 17 00:00:00 2001
From: Andrew Deason <adeason@sinenomine.net>
Date: Thu, 15 Oct 2020 20:30:14 -0500
Subject: [PATCH 5/8] 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

Change-Id: I90c419a7ef0ede247187172a182863dcb4250578
Reviewed-on: https://gerrit.openafs.org/15922
Reviewed-by: Benjamin Kaduk <kaduk@mit.edu>
Tested-by: Benjamin Kaduk <kaduk@mit.edu>
---
 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 53a9449a7c..63d808db78 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 1f5e1ef9e35f6b5e8693c91199c976d5e030c0d0 Mon Sep 17 00:00:00 2001
From: Andrew Deason <adeason@sinenomine.net>
Date: Fri, 16 Oct 2020 10:52:03 -0500
Subject: [PATCH 6/8] 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

Change-Id: I4e058eb52d277e6d3817df8b703c5e5b894c0b5a
Reviewed-on: https://gerrit.openafs.org/15923
Reviewed-by: Benjamin Kaduk <kaduk@mit.edu>
Tested-by: Benjamin Kaduk <kaduk@mit.edu>
---
 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/WINNT/afsd/cm_getaddrs.c b/src/WINNT/afsd/cm_getaddrs.c
index 4db33019d9..d95aa37a79 100644
--- a/src/WINNT/afsd/cm_getaddrs.c
+++ b/src/WINNT/afsd/cm_getaddrs.c
@@ -234,6 +234,7 @@ cm_GetAddrsU(cm_cell_t *cellp, cm_user_t *userp, cm_req_t *reqp,
 	    code = cm_ConnByMServers(cellp->vlServersp, 0, userp, reqp, &connp);
 	    if (code)
 		continue;
+	    xdr_free((xdrproc_t) xdr_bulkaddrs, &addrs);
 	    rxconnp = cm_GetRxConn(connp);
 	    code = VL_GetAddrsU(rxconnp, &attrs, &uuid, &unique, &nentries,
 				 &addrs);
diff --git a/src/afs/afs_pioctl.c b/src/afs/afs_pioctl.c
index 0157bdc269..50ce888ed4 100644
--- a/src/afs/afs_pioctl.c
+++ b/src/afs/afs_pioctl.c
@@ -1599,6 +1599,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 21c729eb32..e792f248c0 100644
--- a/src/afs/afs_volume.c
+++ b/src/afs/afs_volume.c
@@ -1208,6 +1208,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 1a0b1e1ccf..4dbe0f6689 100644
--- a/src/ptserver/pts.c
+++ b/src/ptserver/pts.c
@@ -700,6 +700,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 408f516654..81fe45cd40 100644
--- a/src/volser/vos.c
+++ b/src/volser/vos.c
@@ -4598,6 +4598,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, &centries, &arrayEntries);
 	    nextindex = -1;
-- 
2.45.2


From f31a79d749abc8e64a8d9ac748bb2b5457875099 Mon Sep 17 00:00:00 2001
From: Andrew Deason <adeason@sinenomine.net>
Date: Fri, 16 Oct 2020 10:55:15 -0500
Subject: [PATCH 7/8] 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

Change-Id: I6f87fc8cb5df0298061f419112200f6c7e1974ba
Reviewed-on: https://gerrit.openafs.org/15924
Reviewed-by: Benjamin Kaduk <kaduk@mit.edu>
Tested-by: Benjamin Kaduk <kaduk@mit.edu>
---
 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 c4e28c2afe743aa323be57ef3b0faec13027e678 Mon Sep 17 00:00:00 2001
From: Andrew Deason <adeason@sinenomine.net>
Date: Thu, 15 Oct 2020 21:07:17 -0500
Subject: [PATCH 8/8] 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

Change-Id: I95ee91a1710d66e2e88c3086d57c279a376f7dc6
Reviewed-on: https://gerrit.openafs.org/15925
Reviewed-by: Benjamin Kaduk <kaduk@mit.edu>
Tested-by: Benjamin Kaduk <kaduk@mit.edu>
---
 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 9b7a90631e..0515172e77 100644
--- a/src/afs/afs_pag_call.c
+++ b/src/afs/afs_pag_call.c
@@ -466,6 +466,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 8bf34fcaba..381095f03a 100644
--- a/src/kauth/authclient.c
+++ b/src/kauth/authclient.c
@@ -524,12 +524,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;
@@ -537,6 +540,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

