Skip to content

Commit d4cc791

Browse files
committed
sys/rpc: UNIX auth: Fix OOB reads on too short message
In the inline version (_svcauth_unix()), fix multiple possible OOB reads when the credentials part of a request is too short to contain mandatory fields or with respect to the hostname length or number of groups it advertises. The previously existing check was arriving too late and relied on possibly wrong data coming from earlier OOB reads. While here, use 'uint32_t' as the length/size type, as it is more than enough and removes the need for conversions, explicit or implicit. While here, factor out setting 'stat' to AUTH_BADCRED and then jumping to 'done' on error, through the new 'badcred' label. While here, through comments, refer to what the non-inline version is doing (xdr_authunix_parms() in 'authunix_prot.c') and the reasons. Reviewed by: rmacklem Fixes: dfdcada ("Add the new kernel-mode NFS Lock Manager.") MFC after: 2 days Sponsored by: The FreeBSD Foundation Differential Revision: https://reviews.freebsd.org/D52964
1 parent e665c0f commit d4cc791

File tree

1 file changed

+60
-39
lines changed

1 file changed

+60
-39
lines changed

sys/rpc/svc_auth_unix.c

Lines changed: 60 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -59,70 +59,91 @@ _svcauth_unix(struct svc_req *rqst, struct rpc_msg *msg)
5959
enum auth_stat stat;
6060
XDR xdrs;
6161
int32_t *buf;
62-
uint32_t time;
6362
struct xucred *xcr;
64-
u_int auth_len;
65-
size_t str_len, supp_ngroups;
66-
u_int i;
63+
uint32_t auth_len, time;
6764

6865
xcr = rqst->rq_clntcred;
6966
auth_len = (u_int)msg->rm_call.cb_cred.oa_length;
7067
xdrmem_create(&xdrs, msg->rm_call.cb_cred.oa_base, auth_len,
7168
XDR_DECODE);
7269
buf = XDR_INLINE(&xdrs, auth_len);
7370
if (buf != NULL) {
74-
time = IXDR_GET_UINT32(buf);
75-
str_len = (size_t)IXDR_GET_UINT32(buf);
76-
if (str_len > AUTH_SYS_MAX_HOSTNAME) {
77-
stat = AUTH_BADCRED;
78-
goto done;
71+
/* 'time', 'str_len', UID, GID and 'supp_ngroups'. */
72+
const uint32_t min_len = 5 * BYTES_PER_XDR_UNIT;
73+
uint32_t str_len, supp_ngroups;
74+
75+
if (auth_len < min_len) {
76+
(void)printf("AUTH_SYS: Too short credentials (%u)\n",
77+
auth_len);
78+
goto badcred;
7979
}
80+
time = IXDR_GET_UINT32(buf);
81+
str_len = IXDR_GET_UINT32(buf);
82+
if (str_len > AUTH_SYS_MAX_HOSTNAME)
83+
goto badcred;
8084
str_len = RNDUP(str_len);
85+
/*
86+
* Recheck message length now that we know the value of
87+
* 'str_len' (and that it won't cause an overflow in additions
88+
* below) to protect access to the credentials part.
89+
*/
90+
if (auth_len < min_len + str_len) {
91+
(void)printf("AUTH_SYS: Inconsistent credentials and "
92+
"host name lengths (%u, %u)\n",
93+
auth_len, str_len);
94+
goto badcred;
95+
}
8196
buf += str_len / sizeof (int32_t);
8297
xcr->cr_uid = IXDR_GET_UINT32(buf);
8398
xcr->cr_gid = IXDR_GET_UINT32(buf);
84-
supp_ngroups = (size_t)IXDR_GET_UINT32(buf);
85-
if (supp_ngroups > AUTH_SYS_MAX_GROUPS) {
86-
stat = AUTH_BADCRED;
87-
goto done;
88-
}
89-
for (i = 0; i < supp_ngroups; i++) {
90-
/*
91-
* Note that this is a `struct xucred`, which maintains
92-
* its historical layout of preserving the egid in
93-
* cr_ngroups and cr_groups[0] == egid.
94-
*/
95-
if (i + 1 < XU_NGROUPS)
96-
xcr->cr_groups[i + 1] = IXDR_GET_INT32(buf);
97-
else
98-
buf++;
99+
supp_ngroups = IXDR_GET_UINT32(buf);
100+
/*
101+
* See the herald comment before a similar test at the end of
102+
* xdr_authunix_parms() for why we strictly respect RFC 5531 and
103+
* why we may have to drop the last supplementary group when
104+
* there are AUTH_SYS_MAX_GROUPS of them.
105+
*/
106+
if (supp_ngroups > AUTH_SYS_MAX_GROUPS)
107+
goto badcred;
108+
/*
109+
* Final message length check, as we now know how much we will
110+
* read in total.
111+
*/
112+
if (auth_len < min_len + str_len +
113+
supp_ngroups * BYTES_PER_XDR_UNIT) {
114+
(void)printf("AUTH_SYS: Inconsistent lengths "
115+
"(credentials %u, machine name %u, "
116+
"supplementary groups %u)\n",
117+
auth_len, str_len,
118+
supp_ngroups * BYTES_PER_XDR_UNIT);
119+
goto badcred;
99120
}
100-
if (supp_ngroups + 1 > XU_NGROUPS)
101-
xcr->cr_ngroups = XU_NGROUPS;
102-
else
103-
xcr->cr_ngroups = supp_ngroups + 1;
104121

105122
/*
106-
* five is the smallest unix credentials structure -
107-
* timestamp, hostname len (0), uid, gid, and gids len (0).
123+
* Note that 'xcr' is a 'struct xucred', which still has the
124+
* historical layout where the effective GID is in cr_groups[0]
125+
* and is accounted in 'cr_ngroups'.
108126
*/
109-
if ((5 + supp_ngroups) * BYTES_PER_XDR_UNIT + str_len > auth_len) {
110-
(void) printf("bad auth_len gid %ld str %ld auth %u\n",
111-
(long)supp_ngroups, (long)str_len, auth_len);
112-
stat = AUTH_BADCRED;
113-
goto done;
127+
for (uint32_t i = 0; i < supp_ngroups; ++i) {
128+
if (i < XU_NGROUPS - 1)
129+
xcr->cr_sgroups[i] = IXDR_GET_INT32(buf);
130+
else
131+
buf++;
114132
}
115-
} else if (! xdr_authunix_parms(&xdrs, &time, xcr)) {
116-
stat = AUTH_BADCRED;
117-
goto done;
118-
}
133+
xcr->cr_ngroups = MIN(supp_ngroups + 1, XU_NGROUPS);
134+
} else if (! xdr_authunix_parms(&xdrs, &time, xcr))
135+
goto badcred;
119136

120137
rqst->rq_verf = _null_auth;
121138
stat = AUTH_OK;
122139
done:
123140
XDR_DESTROY(&xdrs);
124141

125142
return (stat);
143+
144+
badcred:
145+
stat = AUTH_BADCRED;
146+
goto done;
126147
}
127148

128149

0 commit comments

Comments
 (0)