Mailing List Archive

Bugs in spa-auth-fixes
Hi,

in a short review of spa-auth-fixes, I found a few issues:

First, the code now tries to avoid reading outside the respective data
structures, but it still reads uninitialized parts of the structure itself,
which still can lead to information disclosure.

For example:

| SPAAuthResponse *responseptr = &response;
| [...]
| if (spa_base64_to_bits(CS &response, sizeof(response), CCS data) < 0)
| [...]
| int len = SVAL(&responseptr->uUser.len,0)/2;

spa_base64_to_bits() returns the number of bytes it has written to
response, which can be less than the end of the uUser field.

Then, the newly introduced code in the macro spa_bytes_add that's
presumably intended to do a bounds check has incorrect operator precedence
for that to work reliably:

| #define spa_bytes_add(ptr, header, buf, count) \
| { \
| -if (buf && (count) != 0) /* we hate -Wint-in-bool-contex */ \
| +if ( buf && (count) != 0 /* we hate -Wint-in-bool-contex */ \
| + && ptr->bufIndex + count < sizeof(ptr->buffer) \
| + ) \
| { \

Combined with another change:

| -spa_bytes_add (response, lmResponse, lmRespData, (cf & 0x200) ? 24 : 0);
| -spa_bytes_add (response, ntResponse, ntRespData, (cf & 0x8000) ? 24 : 0);
| +spa_bytes_add(response, lmResponse, lmRespData, cf & 0x200 ? 24 : 0);
| +spa_bytes_add(response, ntResponse, ntRespData, cf & 0x8000 ? 24 : 0);

Effectively uses the following expression to do the bounds check:

| ((response->bufIndex + cf) & 0x8000) ? 24 : (0 < sizeof(response->buffer))

As 0 < sizeof(response->buffer) is always true, and 24 is, too, this
supposed bounds check always succeeds for these call sites. Luckily, that
probably doesn't cause a vulnerability because the lengths here are not
under the control of the attacker. But my guess is that the code still is
broken because what looks like a flag check doesn't do a flag check
anymore.

And finally, the bounds checks in the newly introduced functions
get_challenge_unistr() and get_challenge_str() don't really work, for one
because they can exhibit undefined behaviour, but also because they don't
limit indices to acceptable values even when the behaviour is defined:

| +static inline uschar *
| +get_challenge_unistr(SPAAuthChallenge * challenge, SPAStrHeader * hdr)
| +{
| +int off = IVAL(&hdr->offset, 0);
| +int len = SVAL(&hdr->len, 0);
| +return off + len < sizeof(SPAAuthChallenge)
| + ? US unicodeToString(CS challenge + off, len/2) : US"";
| +}

Assuming that int is 32 bit, 'off' is 32 bit signed, IVAL() returns an
attacker-controlled 32 bit unsigned value, so via usual
implementation-defined behaviour, the attacker can make 'off' negative,
which isn't detected by the bounds check, and thus can be used to read data
that's stored before 'challenge'.

Undefined behaviour results if 'off' is INT_MAX and 'len' is non-zero.
Usually, that'll lead to overflow in the addition, also resulting in a
negative value that passes the bounds check, thus allowing to read data
roughly INT_MAX behind 'challenge', which presumably would be a different
location with 64 bit pointers.

Regards, Florian

--
## subscription configuration (requires account):
## https://lists.exim.org/mailman3/postorius/lists/exim-dev.lists.exim.org/
## unsubscribe (doesn't require an account):
## exim-dev-unsubscribe@lists.exim.org
## Exim details at http://www.exim.org/
## Please use the Wiki with this list - http://wiki.exim.org/
Re: Bugs in spa-auth-fixes [ In reply to ]
Hi,

> | +static inline uschar *
> | +get_challenge_unistr(SPAAuthChallenge * challenge, SPAStrHeader * hdr)
> | +{
> | +int off = IVAL(&hdr->offset, 0);
> | +int len = SVAL(&hdr->len, 0);
> | +return off + len < sizeof(SPAAuthChallenge)
> | + ? US unicodeToString(CS challenge + off, len/2) : US"";
> | +}
>
> Assuming that int is 32 bit, 'off' is 32 bit signed, IVAL() returns an
> attacker-controlled 32 bit unsigned value, so via usual
> implementation-defined behaviour, the attacker can make 'off' negative,
> which isn't detected by the bounds check, and thus can be used to read data
> that's stored before 'challenge'.
>
> Undefined behaviour results if 'off' is INT_MAX and 'len' is non-zero.
> Usually, that'll lead to overflow in the addition, also resulting in a
> negative value that passes the bounds check, thus allowing to read data
> roughly INT_MAX behind 'challenge', which presumably would be a different
> location with 64 bit pointers.

Just in case anyone hasn't noticed yet: That was a bit of a brain fart,
apparently I overlooked that sizeof gives a size_t or something ...

But it's still correct that the bounds check doesn't work, just the
explanation is somewhere between confusing and incorrect.

So: Assuming that int is 32 bit, 'off' is 32 bit signed, IVAL() returns an
attacker-controlled 32 bit unsigned value, so via usual
implementation-defined behaviour, the attacker can make 'off' negative, and
then, if len is large enough to trigger undefined behaviour in the
addition, can usually cause off + len to be positive again, and, if smaller
than sizeof(SPAAuthChallenge), can thus pass the bounds check, which thus
can be used to read up to 64 KiB before SPAAuthChallenge.

Regards, Florian

--
## subscription configuration (requires account):
## https://lists.exim.org/mailman3/postorius/lists/exim-dev.lists.exim.org/
## unsubscribe (doesn't require an account):
## exim-dev-unsubscribe@lists.exim.org
## Exim details at http://www.exim.org/
## Please use the Wiki with this list - http://wiki.exim.org/
Re: Bugs in spa-auth-fixes [ In reply to ]
Hi,

> > | +static inline uschar *
> > | +get_challenge_unistr(SPAAuthChallenge * challenge, SPAStrHeader * hdr)
> > | +{
> > | +int off = IVAL(&hdr->offset, 0);
> > | +int len = SVAL(&hdr->len, 0);
> > | +return off + len < sizeof(SPAAuthChallenge)
> > | + ? US unicodeToString(CS challenge + off, len/2) : US"";
> > | +}
>
> So: Assuming that int is 32 bit, 'off' is 32 bit signed, IVAL() returns an
> attacker-controlled 32 bit unsigned value, so via usual
> implementation-defined behaviour, the attacker can make 'off' negative, and
> then, if len is large enough to trigger undefined behaviour in the
> addition, can usually cause off + len to be positive again, and, if smaller
> than sizeof(SPAAuthChallenge), can thus pass the bounds check, which thus
> can be used to read up to 64 KiB before SPAAuthChallenge.

Erm ... obviously (or so you'd think), that's nonsense. I mean, the attack
isn't, but there is no undefined behaviour in that addition in that
scenario.

But of course, the attacker can *also* trigger undefined behaviour in that
addition ... though that's probably usually harmless because the generated
code probably usually would just trigger the bounds check. But then, it's
probably not a good idea to have undefined behaviour anyway ...

Regards, Florian

--
## subscription configuration (requires account):
## https://lists.exim.org/mailman3/postorius/lists/exim-dev.lists.exim.org/
## unsubscribe (doesn't require an account):
## exim-dev-unsubscribe@lists.exim.org
## Exim details at http://www.exim.org/
## Please use the Wiki with this list - http://wiki.exim.org/