Skip to content

jas_seq.c: out-of-bounds read in public jas_matrix_bindsub API #419

Description

@huanghuihui0904

jas_matrix_bindsub_asan.log

Describe the bug

jas_matrix_bindsub is a public API function declared with JAS_EXPORT, and it is also reachable through the inline wrappers jas_matrix_bindrow and jas_matrix_bindcol declared in the public header.
The function accepts caller-supplied row/column indices and uses them to index into mat1->rows_ with no validation against mat1->numrows_ / mat1->numcols_.
I found this with KLEE symbolic execution of src/libjasper/base/jas_seq.c and reproduced it under AddressSanitizer.
It is present in upstream master as of June 1, 2026.

Out-of-bounds row-pointer read at jas_seq.c:242

// src/libjasper/base/jas_seq.c:241-243, inside jas_matrix_bindsub
for (i = 0; i < mat0->numrows_; ++i) {
    mat0->rows_[i] = mat1->rows_[r0 + i] + c0;   // <-- ASan: heap-buffer-overflow / SEGV
}

r0, r1, c0, c1 are passed straight through from the caller (a jas_matind_t, i.e. signed integer).
With r0 + i >= mat1->numrows_ the load reads past the end of the mat1->rows_ pointer array; the read value, which is an invalid row pointer, is then stored into mat0->rows_[i], so any subsequent matrix-element accessor on mat0 dereferences it.

The sibling entry jas_seq2d_bindsub (jas_seq.c:206) does validate its inputs before forwarding to jas_matrix_bindsub, but the public inline wrappers in src/libjasper/include/jasper/jas_seq.h do not:

// src/libjasper/base/jas_seq.c:209-212, inside jas_seq2d_bindsub
if (xstart < s1->xstart_ || ystart < s1->ystart_ ||
    xend > s1->xend_ || yend > s1->yend_) {
    return -1;
}

By contrast, the public inline wrapper used by the PoC forwards its caller-supplied row index directly:

// src/libjasper/include/jasper/jas_seq.h:316-319
static inline int jas_matrix_bindrow(jas_matrix_t *mat0, jas_matrix_t *mat1, jas_matind_t r)
{
    return jas_matrix_bindsub(mat0, mat1, r, 0, r, mat1->numcols_ - 1);
}

The centralized fix belongs in jas_matrix_bindsub because callers can also invoke that exported function directly.

To Reproduce

Standalone reproducer using only the public headers.
After cloning JasPer, save this as jasper/poc_jas_matrix_bindsub.c in the root of the JasPer source checkout:

#include <jasper/jasper.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) {
    int near = (argc > 1) && (strcmp(argv[1], "--near") == 0);

    if (jas_init()) return 1;

    jas_matrix_t *m   = jas_matrix_create(4, 4);
    jas_matrix_t *out = jas_matrix_create(1, 1);
    if (!m || !out) return 1;

    jas_matind_t r = near ? 8 : 1000000;
    fprintf(stderr, "[poc] jas_matrix_bindrow(out, m{4x4}, r=%ld)\n", (long)r);

    int rc = jas_matrix_bindrow(out, m, r);
    fprintf(stderr, "[poc] bindrow returned %d\n", rc);

    if (rc == 0) {
        fprintf(stderr, "[poc] jas_matrix_get(out, 0, 0)\n");
        jas_seqent_t v = jas_matrix_get(out, 0, 0);
        fprintf(stderr, "[poc] value=%ld (should not reach this under ASan)\n", (long)v);
    }

    jas_matrix_destroy(m);
    jas_matrix_destroy(out);
    jas_cleanup();
    return 0;
}

For r = 8, AddressSanitizer reports the out-of-bounds read while jas_matrix_bindrow is executing; the follow-on jas_matrix_get call is not required to trigger this report.

The following commands reproduce the issue from a clean checkout.
Run them from the root of the JasPer source checkout.
The build directory is created next to the checkout because JasPer rejects build directories located inside its source tree:

git clone https://github.com/jasper-software/jasper.git
cd jasper
git checkout 5ce57f67e0011e0d2483992a5d77a092b1b64eb7

cmake -S . -B ../jas_repro_build -DCMAKE_BUILD_TYPE=Debug -DJAS_ENABLE_SHARED=ON -DJAS_ENABLE_PROGRAMS=OFF -DJAS_ENABLE_DOC=OFF -DCMAKE_C_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer -g -O0" -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address,undefined" -DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=address,undefined"
cmake --build ../jas_repro_build --target libjasper -j

gcc -g -O0 -fsanitize=address,undefined -fno-omit-frame-pointer -I../jas_repro_build/src/libjasper/include -I"$PWD/src/libjasper/include" poc_jas_matrix_bindsub.c -L../jas_repro_build/src/libjasper -ljasper -Wl,-rpath,"$PWD/../jas_repro_build/src/libjasper" -o poc_jas_matrix_bindsub

ASAN_OPTIONS=abort_on_error=0:halt_on_error=1 ./poc_jas_matrix_bindsub --near 2>&1 | tee jas_matrix_bindsub_asan.log

The expected result for --near is:

[poc] jas_matrix_bindrow(out, m{4x4}, r=8)
=================================================================
==PID==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6060000002c0
READ of size 8 at 0x6060000002c0 thread T0
    #0 jas_matrix_bindsub .../jasper/src/libjasper/base/jas_seq.c:242
    #1 jas_matrix_bindrow .../jasper/src/libjasper/include/jasper/jas_seq.h:318
    #2 main .../jasper/poc_jas_matrix_bindsub.c:18

Address 0x6060000002c0 is a wild pointer.
SUMMARY: AddressSanitizer: heap-buffer-overflow .../jasper/src/libjasper/base/jas_seq.c:242 in jas_matrix_bindsub

The deprecation warning for jas_init and the warning about the default JasPer memory limit may also appear before the ASan report; they are unrelated to this out-of-bounds read.
The full AddressSanitizer output is attached at the top of this report.

Affected version: latest upstream master as of June 1, 2026 @ 5ce57f67e0011e0d2483992a5d77a092b1b64eb7.

Expected behavior

Reject out-of-range r0 / r1 / c0 / c1 at entry to jas_matrix_bindsub — and therefore for the inline jas_matrix_bindrow / jas_matrix_bindcol wrappers — before allocating mat0->rows_ or executing the row-copy loop.

Suggested fix direction

Validate r0, r1, c0, and c1 against the source matrix dimensions at the entry to jas_matrix_bindsub, before allocating or copying row pointers.
This follows the validation approach already used by jas_seq2d_bindsub in the same file.

For example:

diff --git a/src/libjasper/base/jas_seq.c b/src/libjasper/base/jas_seq.c
--- a/src/libjasper/base/jas_seq.c
+++ b/src/libjasper/base/jas_seq.c
@@ -219,6 +219,11 @@ int jas_matrix_bindsub(jas_matrix_t *mat0, jas_matrix_t *mat1,
 {
 	jas_matind_t i;
 
+	if (r0 < 0 || r1 < r0 || r1 >= mat1->numrows_ ||
+	  c0 < 0 || c1 < c0 || c1 >= mat1->numcols_) {
+		return -1;
+	}
+
 	if (mat0->data_) {
 		if (!(mat0->flags_ & JAS_MATRIX_REF)) {
 			jas_free(mat0->data_);

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions