Skip to content

Bound sparse writes to nir in Mat_VarWrite4 - #381

Merged
tbeu merged 1 commit into
tbeu:masterfrom
naruto-lgtm:mat4-sparse-write-nir-bound
Oct 20, 2026
Merged

tbeu merged 1 commit into
tbeu:masterfrom
naruto-lgtm:mat4-sparse-write-nir-bound

Conversation

@naruto-lgtm

Copy link
Copy Markdown
Contributor

Repro: read a v5 sparse variable whose ir tag holds 1 index, with jc = {0, 64} and 64 doubles of data, then write it to a v4 file (test_mat -v 4 copy sparse.mat). ASan reports a heap-buffer-overflow READ of size 4 at mat4.c:209, 0 bytes after the 4-byte ir allocation. Without ASan, 63 out-of-bounds uint32 values from the heap end up in the output file.
Cause: Mat_VarRead5 takes nir, njc, the jc values and ndata from separate file tags, so they can disagree. The sparse branch of Mat_VarWrite4 bounds each jc[i]..jc[i+1] walk by ndata only and then reads ir[j]. Mat_VarPrint already bounds the same walk by both ndata and nir.
Fix: add the same j < sparse->nir bound to all five jc loops, so the row, column and value streams stay the same length. Valid sparse arrays (jc[njc-1] <= nir == ndata) are written byte-identically. All 6262 ctest cases pass under ASan/UBSan (non-HDF5 build).

Generator for the crafted file:

import struct
def el(t, p):
    b = struct.pack('<II', t, len(p)) + p
    return b + b'\0' * ((-len(b)) % 8)
hdr = b'MATLAB 5.0 MAT-file'.ljust(116, b' ') + b'\0' * 8 + struct.pack('<H', 0x0100) + b'IM'
body = el(6, struct.pack('<II', 5, 64)) + el(5, struct.pack('<ii', 1, 1)) + el(1, b'x')
body += el(5, struct.pack('<I', 0)) + el(5, struct.pack('<II', 0, 64))
body += el(9, struct.pack('<64d', *range(64)))
open('sparse.mat', 'wb').write(hdr + el(14, body))

@tbeu
tbeu merged commit 67ffbfa into tbeu:master Oct 20, 2026
73 of 74 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants