Skip to content

bitfield: fix out-of-bounds read in find_last_bit() - #1068

Open
gargamel778 wants to merge 1 commit into
OpenIntelWireless:masterfrom
gargamel778:fix-find-last-bit-oob
Open

gargamel778 wants to merge 1 commit into
OpenIntelWireless:masterfrom
gargamel778:fix-find-last-bit-oob

Conversation

@gargamel778

Copy link
Copy Markdown

find_last_bit() never reads the word it is asked to search, and reads one word past the end of the caller's object instead.

The bug

for (b = max; b > 0; b -= 32) {
    if (ptr[b >> 5] != 0) {
        for (;;) {
            if (ptr[b >> 5] & (1 << (b & 0x1f))) return b;
            b--;
        }
    }
}
return max;

The only caller is rs_get_max_rate_from_mask() (hal_iwm/rs.cpp:1213), which passes &rate_mask — an 8-byte unsigned long local — with max = BITS_PER_LONG = 64:

iteration reads result
b = 64 ptr[64>>5] = ptr[2] bytes 8–11 of an 8-byte object — out of bounds
b = 32 ptr[1] bytes 4–7, always zero (rate masks are ≤ 17 bits)
exit b > 0 fails ptr[0], where the whole mask lives, is never examined

It returns max (64), so the result is decided by whatever the compiler placed after the local on the stack.

Checking it without any hardware

rs.cpp:2988 already logs the result:

XYLog("MAX RATE: LEGACY=%d SISO=%d MIMO2=%d\n", ...)

The maximum legal index is IWL_RATE_COUNT - 1 = 16. On an unpatched build here it prints:

itlwm: MAX RATE: LEGACY=64 SISO=64 MIMO2=64

and with the patch:

itlwm: LEGACY=FF0 SISO=7FD0 MIMO2=7FD0 VHT=1 LDPC=1 STBC=0 BFER=0
itlwm: MAX RATE: LEGACY=11 SISO=14 MIMO2=14

Occurrences of MAX RATE ...=64 went from 23 of 50 station rate-inits to 0 of 33 across a full test protocol on an Intel 7265.

Why 64 is worse than an error

IWL_RATE_INVALID == IWL_RATE_COUNT == 17 (rs.h:1007), so 64 is not caught by the invalid-index guards. It is then used to index expected_tpt_tbl[] — a second out-of-bounds read — and rs_init_optimal_rate() compares max_mimo2_rate_idx against IWL_RATE_INVALID, so an index pinned at 64 always takes the MIMO2 branch.

Standalone reproducer

Compiled against the unpatched function, varying only the stack word that follows the mask:

rate_mask   correct |   upstream find_last_bit(), by ptr[2] value
                    |   0x0    0x1  0xdeadbeef   0x2   0xfffffffe
0x1         0       |     64     64     64        0      0
0xff        7       |     64     64     64        7      7
0x1ff0      12      |     64     64     64       12     12
0x1ffff     16      |     64     64     64       16     16

The fix

Scan downwards from max - 1, which restores Linux's documented contract — "the bit number of the last set bit, or size". Eight lines, header-only, one caller.


Scope: this is a correctness and memory-safety fix, evidenced by the wrong value in the driver's own log line. I am not claiming it improves throughput or fixes disconnects — I have no measurement supporting that, and I would rather not imply one.

The loop started at b == max and tested ptr[b >> 5] before decrementing,
so for the only caller -- rs_get_max_rate_from_mask(), which passes an
8-byte object with max == BITS_PER_LONG == 64 -- the first iteration read
ptr[2], eight bytes past the end of the object, then ptr[1] (always zero:
rate masks are <= 17 bits), then exited at b == 0 without ever examining
ptr[0], where every bit of the mask lives.

The return value therefore depends on whatever follows the mask on the
stack: 64 when ptr[2] is zero or has bit 0 set, and by luck the correct
answer otherwise.  64 is not IWL_RATE_INVALID (== IWL_RATE_COUNT == 17),
so it passes the guards in rs_get_max_allowed_rate()'s callers and is
used to index expected_tpt_tbl[], a second out-of-bounds read -- 94 bytes
past the end of expected_tpt_legacy[IWL_RATE_COUNT] for the RS_LEGACY
column.  rs_init_optimal_rate() also compares max_mimo2_rate_idx against
IWL_RATE_INVALID, so a value pinned at 64 always selects the MIMO2 branch.

Scan downwards from max - 1 instead, which restores Linux's contract:
"the bit number of the last set bit, or size".
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant