[PATCH 3.2 4/4] rxrpc: Fix several cases where a padded len isn't checked in ticket decode

From: Ben Hutchings
Date: Tue Jun 27 2017 - 07:11:06 EST


3.2.90-rc1 review patch. If anyone has any objections, please let me know.

------------------

From: David Howells <dhowells@xxxxxxxxxx>

commit 5f2f97656ada8d811d3c1bef503ced266fcd53a0 upstream.

This fixes CVE-2017-7482.

When a kerberos 5 ticket is being decoded so that it can be loaded into an
rxrpc-type key, there are several places in which the length of a
variable-length field is checked to make sure that it's not going to
overrun the available data - but the data is padded to the nearest
four-byte boundary and the code doesn't check for this extra. This could
lead to the size-remaining variable wrapping and the data pointer going
over the end of the buffer.

Fix this by making the various variable-length data checks use the padded
length.

Reported-by: çç <shilei-c@xxxxxx>
Signed-off-by: David Howells <dhowells@xxxxxxxxxx>
Reviewed-by: Marc Dionne <marc.c.dionne@xxxxxxxxxxxx>
Reviewed-by: Dan Carpenter <dan.carpenter@xxxxxxxxxx>
Signed-off-by: David S. Miller <davem@xxxxxxxxxxxxx>
[bwh: Backported to 3.2: adjust filename, context]
Signed-off-by: Ben Hutchings <ben@xxxxxxxxxxxxxxx>
---
net/rxrpc/ar-key.c | 64 ++++++++++++++++++++++++++++++---------------------------
1 file changed, 34 insertions(+), 30 deletions(-)

--- a/net/rxrpc/ar-key.c
+++ b/net/rxrpc/ar-key.c
@@ -213,7 +213,7 @@ static int rxrpc_krb5_decode_principal(s
unsigned *_toklen)
{
const __be32 *xdr = *_xdr;
- unsigned toklen = *_toklen, n_parts, loop, tmp;
+ unsigned int toklen = *_toklen, n_parts, loop, tmp, paddedlen;

/* there must be at least one name, and at least #names+1 length
* words */
@@ -243,16 +243,16 @@ static int rxrpc_krb5_decode_principal(s
toklen -= 4;
if (tmp <= 0 || tmp > AFSTOKEN_STRING_MAX)
return -EINVAL;
- if (tmp > toklen)
+ paddedlen = (tmp + 3) & ~3;
+ if (paddedlen > toklen)
return -EINVAL;
princ->name_parts[loop] = kmalloc(tmp + 1, GFP_KERNEL);
if (!princ->name_parts[loop])
return -ENOMEM;
memcpy(princ->name_parts[loop], xdr, tmp);
princ->name_parts[loop][tmp] = 0;
- tmp = (tmp + 3) & ~3;
- toklen -= tmp;
- xdr += tmp >> 2;
+ toklen -= paddedlen;
+ xdr += paddedlen >> 2;
}

if (toklen < 4)
@@ -261,16 +261,16 @@ static int rxrpc_krb5_decode_principal(s
toklen -= 4;
if (tmp <= 0 || tmp > AFSTOKEN_K5_REALM_MAX)
return -EINVAL;
- if (tmp > toklen)
+ paddedlen = (tmp + 3) & ~3;
+ if (paddedlen > toklen)
return -EINVAL;
princ->realm = kmalloc(tmp + 1, GFP_KERNEL);
if (!princ->realm)
return -ENOMEM;
memcpy(princ->realm, xdr, tmp);
princ->realm[tmp] = 0;
- tmp = (tmp + 3) & ~3;
- toklen -= tmp;
- xdr += tmp >> 2;
+ toklen -= paddedlen;
+ xdr += paddedlen >> 2;

_debug("%s/...@%s", princ->name_parts[0], princ->realm);

@@ -289,7 +289,7 @@ static int rxrpc_krb5_decode_tagged_data
unsigned *_toklen)
{
const __be32 *xdr = *_xdr;
- unsigned toklen = *_toklen, len;
+ unsigned int toklen = *_toklen, len, paddedlen;

/* there must be at least one tag and one length word */
if (toklen <= 8)
@@ -303,6 +303,9 @@ static int rxrpc_krb5_decode_tagged_data
toklen -= 8;
if (len > max_data_size)
return -EINVAL;
+ paddedlen = (len + 3) & ~3;
+ if (paddedlen > toklen)
+ return -EINVAL;
td->data_len = len;

if (len > 0) {
@@ -310,9 +313,8 @@ static int rxrpc_krb5_decode_tagged_data
if (!td->data)
return -ENOMEM;
memcpy(td->data, xdr, len);
- len = (len + 3) & ~3;
- toklen -= len;
- xdr += len >> 2;
+ toklen -= paddedlen;
+ xdr += paddedlen >> 2;
}

_debug("tag %x len %x", td->tag, td->data_len);
@@ -384,7 +386,7 @@ static int rxrpc_krb5_decode_ticket(u8 *
const __be32 **_xdr, unsigned *_toklen)
{
const __be32 *xdr = *_xdr;
- unsigned toklen = *_toklen, len;
+ unsigned int toklen = *_toklen, len, paddedlen;

/* there must be at least one length word */
if (toklen <= 4)
@@ -396,6 +398,9 @@ static int rxrpc_krb5_decode_ticket(u8 *
toklen -= 4;
if (len > AFSTOKEN_K5_TIX_MAX)
return -EINVAL;
+ paddedlen = (len + 3) & ~3;
+ if (paddedlen > toklen)
+ return -EINVAL;
*_tktlen = len;

_debug("ticket len %u", len);
@@ -405,9 +410,8 @@ static int rxrpc_krb5_decode_ticket(u8 *
if (!*_ticket)
return -ENOMEM;
memcpy(*_ticket, xdr, len);
- len = (len + 3) & ~3;
- toklen -= len;
- xdr += len >> 2;
+ toklen -= paddedlen;
+ xdr += paddedlen >> 2;
}

*_xdr = xdr;
@@ -551,7 +555,7 @@ static int rxrpc_instantiate_xdr(struct
{
const __be32 *xdr = data, *token;
const char *cp;
- unsigned len, tmp, loop, ntoken, toklen, sec_ix;
+ unsigned int len, paddedlen, loop, ntoken, toklen, sec_ix;
int ret;

_enter(",{%x,%x,%x,%x},%zu",
@@ -576,22 +580,21 @@ static int rxrpc_instantiate_xdr(struct
if (len < 1 || len > AFSTOKEN_CELL_MAX)
goto not_xdr;
datalen -= 4;
- tmp = (len + 3) & ~3;
- if (tmp > datalen)
+ paddedlen = (len + 3) & ~3;
+ if (paddedlen > datalen)
goto not_xdr;

cp = (const char *) xdr;
for (loop = 0; loop < len; loop++)
if (!isprint(cp[loop]))
goto not_xdr;
- if (len < tmp)
- for (; loop < tmp; loop++)
- if (cp[loop])
- goto not_xdr;
+ for (; loop < paddedlen; loop++)
+ if (cp[loop])
+ goto not_xdr;
_debug("cellname: [%u/%u] '%*.*s'",
- len, tmp, len, len, (const char *) xdr);
- datalen -= tmp;
- xdr += tmp >> 2;
+ len, paddedlen, len, len, (const char *) xdr);
+ datalen -= paddedlen;
+ xdr += paddedlen >> 2;

/* get the token count */
if (datalen < 12)
@@ -612,10 +615,11 @@ static int rxrpc_instantiate_xdr(struct
sec_ix = ntohl(*xdr);
datalen -= 4;
_debug("token: [%x/%zx] %x", toklen, datalen, sec_ix);
- if (toklen < 20 || toklen > datalen)
+ paddedlen = (toklen + 3) & ~3;
+ if (toklen < 20 || toklen > datalen || paddedlen > datalen)
goto not_xdr;
- datalen -= (toklen + 3) & ~3;
- xdr += (toklen + 3) >> 2;
+ datalen -= paddedlen;
+ xdr += paddedlen >> 2;

} while (--loop > 0);