CVE-2021-30737, @xerub's 2021 iOS ASN.1 Vulnerability
Rather subtle bug in the ASN.1 parsing state machine that comes down to one area of code being unaware of an edge case in another.
A brief bit of background here, ASN.1 or Abstract Syntax Notation One is basically a Type-Length-Value (TLV) style encoding system. You provide is some bytes reflecting the type of object the data is, the length of it, and finally the values. It parses the value as the respective type. Its a bit more complex as it has the concept of primitive
and complex
types. A primtive type is a type where the value bytes can be interpreted directly as their value. Like a byte string, the bytes in the value section are just the bytes of the string. Complex types are made up of further items (sub-items) that need to be parsed until it reaches the primitive types. It also supports encoding data when you don’t know the length ahead of time, or indefinite length values.
Its also worth calling out that there are very few bounds checks in this code. Instead the code relies on the fact that ASN.1 doesn’t support compression or any type of data expansion. So if you have some ASN.1 encoded item that has a Length of 10, the result of parsing those 10 bytes will be no larger than 10 bytes. If its a complex type (has sub-items to parse) it’ll actually be smaller once the metadata is removed. And so when it is parsing something with a defined length, it allocates a buffer for at least as many bytes as are in the length value, and then each sub-item will write the parsed value into that buffer.
For indefinite length values it creates a linked list, allocating a new buffer for each sub-item and concatenating them together at the end.
The root of this bug is when parsing sub-bitstrings and it encounters an empty bitstring as one of the sub-items. That is it has a type of bitstring but no actual content. When the parsing engine encounters this it will try to skip ahead in processing a bit, setting the destination’s Data
buffer to NULL
, and Length
to 0
. Then fast-forwarding the state to beforeEndOfContents
basically just telling the parsing engine to carry on.
It makes sense on the surface, but this actually triggers an edge case when it starts processing the next sub-item. Recall that indefinite length items exist, the way the parsing engine detects that the substring is part of a indefinite length item instead is that Data
will be set to NULL
and Length
to 0
, This tells it that it should go ahead an allocate a new buffer just for this sub-item. Except we can trigger this situation from inside the sub-items for a definite length value with an empty bitstring. When we do that, we can trick the parser into allocating a new buffer, sized only for the current sub-item.
Then when it proceeds to process the next sub-item after that, this one will see that Data
is not NULL
and not recognize that it was allocated by a sibling sub-item, instead assume it was allocated by the parent item, and thus large enough for its content. So this third sub-item can overflow the buffer allocated by the second item.
So the final vulnerability, leading to an out of bounds write it to start the ASN.1 encoded data with defined length bitstring, and three substrings making it up.
- A 0 length bitstring, that triggers the bug setting
Data
toNULL
- A small bitstring, that because
Data
isNULL
will result in a small buffer being placed intoData
- A long bitstring, that will write into
Data
assuming it was allocated with enough space.