Contents
block/ (4 functions) — 0 real, 5 FP
block/partitions/ (13 functions) — 1 real, 62 FP
aix_partition() — aix.c FP
amiga_partition() — amiga.c FP
alloc_read_gpt_entries() — efi.c FP
efi_partition() — efi.c FP
find_valid_gpt() — efi.c FP
utf16_le_to_7bit() — efi.c FP
ldm_frag_add() — ldm.c MIXED
mac_partition() — mac.c FP
parse_bsd() — msdos.c FP
parse_solaris_x86() — msdos.c FP
osf_partition() — osf.c FP
sun_partition() — sun.c FP
sysv68_partition() — sysv68.c FP
block/ (4 functions) — 0 real, 5 FP
Summary
| Function | File | Assessment | Confidence | Real | FP | Unanalyzed |
|---|---|---|---|---|---|---|
| block/ — 4 functions, 0 real, 5 FP | ||||||
| bsg_transport_sg_io_fn() | block/bsg-lib.c | FP | high | 0 | 1 | 0 |
| blkdev_pr_read_keys() | block/ioctl.c | FP | high | 0 | 1 | 0 |
| block/partitions/ — 13 functions, 1 real, 62 FP | ||||||
| aix_partition() | block/partitions/aix.c | FP | high | 0 | 16 | 0 |
| amiga_partition() | block/partitions/amiga.c | FP | high | 0 | 1 | 0 |
| alloc_read_gpt_entries() | block/partitions/efi.c | FP | high | 0 | 2 | 0 |
| efi_partition() | block/partitions/efi.c | FP | high | 0 | 1 | 0 |
| find_valid_gpt() | block/partitions/efi.c | FP | high | 0 | 1 | 0 |
| utf16_le_to_7bit() | block/partitions/efi.c | FP | high | 0 | 1 | 0 |
| ldm_frag_add() | block/partitions/ldm.c | MIXED | high | 1 | 14 | 0 |
| mac_partition() | block/partitions/mac.c | FP | high | 0 | 21 | 0 |
| parse_bsd() | block/partitions/msdos.c | FP | high | 0 | 1 | 0 |
| parse_solaris_x86() | block/partitions/msdos.c | FP | high | 0 | 1 | 0 |
| osf_partition() | block/partitions/osf.c | FP | high | 0 | 1 | 0 |
| sun_partition() | block/partitions/sun.c | FP | high | 0 | 1 | 0 |
| sysv68_partition() | block/partitions/sysv68.c | FP | high | 0 | 1 | 0 |
| block/ — 4 functions, 0 real, 5 FP | ||||||
| opal_discovery0_end() | block/sed-opal.c | FP | high | 0 | 2 | 0 |
| response_parse() | block/sed-opal.c | FP | medium | 0 | 1 | 0 |
Function Details
bsg_transport_sg_io_fn() — block/bsg-lib.c FP confidence=high
The function correctly uses min() to bound the copy size against the kernel-internal job->reply_len before calling copy_to_user(). hdr->max_response_len is user-supplied but is constrained by the kernel-controlled reply buffer length. The destination of copy_to_user is user space, so there is no kernel buffer to overflow.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 118 |
| Taint snippet | if (copy_to_user(uptr64(hdr->response), job->reply, len)) |
| Tainted var | len |
| Unvalidated size | copy_to_user() arg 2 line 118 — size len |
| Sink snippet | if (copy_to_user(uptr64(hdr->response), job->reply, len)) |
| Possibly guarded | no |
Dismissed: len = min(hdr->max_response_len, job->reply_len). job->reply_len is set to SCSI_SENSE_BUFFERSIZE (kernel constant) or sizeof(u32) by kernel code — not user-controlled. The min() ensures len never exceeds the actual reply buffer size. Counterexample attempt: hdr->max_response_len=UINT_MAX, job->reply_len=96 yields len=96, which is exactly the buffer size — no OOB. copy_to_user writes to user space, so there is no fixed-size kernel destination buffer that could be overflowed. Finding is a false positive.
blkdev_pr_read_keys() — block/ioctl.c FP confidence=high
The function properly validates the user-supplied num_keys against PR_KEYS_MAX before allocation, and uses min() to cap the copy length. The keys_copy_len is bounded by the validated read_keys.num_keys, which equals the number of entries allocated in keys_info->keys, making the copy safe from the kernel buffer side. The destination is a user pointer, which is appropriate for copy_to_user.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 470 |
| Taint snippet | if (copy_to_user(keys_ptr, keys_info->keys, keys_copy_len)) { |
| Tainted var | keys_copy_len |
| Unvalidated size | copy_to_user() arg 2 line 470 — size keys_copy_len |
| Sink snippet | if (copy_to_user(keys_ptr, keys_info->keys, keys_copy_len)) { |
| Possibly guarded | no |
Dismissed: keys_copy_len = min(read_keys.num_keys, keys_info->num_keys) * sizeof(...). Since read_keys.num_keys was validated to be <= PR_KEYS_MAX before allocation, and keys_info was allocated for exactly read_keys.num_keys entries, the min() ensures we never read beyond the allocated buffer. No counterexample can be constructed: any value of keys_info->num_keys larger than read_keys.num_keys would be capped by min(), and read_keys.num_keys <= PR_KEYS_MAX was checked at line 450. The scanner flagged this because the size involves a user-supplied field, but the combination of the upper-bound check and min() makes it safe.
aix_partition() — block/partitions/aix.c FP confidence=high
The code has reasonable validation: (1) the numlvs loop has two additional upper-bound guards (state->limit and sector size); (2) pvd is a locally kernel-allocated struct of known fixed size, not a raw server-supplied pointer; (3) numpps is clamped to ARRAY_SIZE(pvd->ppe) before the loop; (4) lv_ix has an explicit bounds check before all uses; (5) put_partition() has its own internal bounds check. The scanner incorrectly propagated taint through alloc_pvd() treating the returned kernel allocation as a tainted offset.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | be16_to_cpu() line 197 |
| Taint snippet | numlvs = be16_to_cpu(p->numlvs); |
| Tainted var | numlvs |
| Loop | for_loop line 217 |
| Sink snippet | for (i = 0; foundlvs < numlvs && i < state->limit && |
| Possibly guarded | no |
Dismissed: Loop has two additional guards: i < state->limit (bounds lvip array) and i < SECTOR_SIZE/sizeof(struct lvd) (bounds sector buffer). numlvs being arbitrarily large just causes early termination. No counterexample possible: to go OOB on lvip, i would need >= state->limit, but that guard fires first.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be32_to_cpu() line 186 |
| Taint snippet | vgda_sector = be32_to_cpu(p->vgda_psn[0]); |
| Tainted var | pvd |
| Pointer deref | pvd->pp_count line 229 |
| Sink snippet | int numpps = be16_to_cpu(pvd->pp_count); |
| Possibly guarded | no |
Dismissed: pvd is NOT a pointer into a raw server buffer. alloc_pvd() calls kmalloc(sizeof(struct pvd)) then reads exactly sizeof(struct pvd) bytes into it, returning NULL on short reads. pvd->pp_count is a field within a fully kernel-allocated struct. Scanner incorrectly propagated taint from vgda_sector through alloc_pvd().
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be32_to_cpu() line 186 |
| Taint snippet | vgda_sector = be32_to_cpu(p->vgda_psn[0]); |
| Tainted var | pvd |
| Pointer deref | pvd->psn_part1 line 230 |
| Sink snippet | int psn_part1 = be32_to_cpu(pvd->psn_part1); |
| Possibly guarded | no |
Dismissed: Same as #2: pvd->psn_part1 is a field in a locally allocated struct pvd of known size. False positive from incorrect taint propagation through alloc_pvd().
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be32_to_cpu() line 186 |
| Taint snippet | vgda_sector = be32_to_cpu(p->vgda_psn[0]); |
| Tainted var | pvd |
| Pointer deref | pvd->ppe line 242 |
| Sink snippet | if (numpps > ARRAY_SIZE(pvd->ppe)) |
| Possibly guarded | no |
Dismissed: pvd->ppe is an array field within the locally allocated struct pvd. ARRAY_SIZE(pvd->ppe) is a compile-time constant. No OOB possible.
Finding #5 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be32_to_cpu() line 186 |
| Taint snippet | vgda_sector = be32_to_cpu(p->vgda_psn[0]); |
| Tainted var | pvd |
| Pointer deref | pvd->ppe line 243 |
| Sink snippet | numpps = ARRAY_SIZE(pvd->ppe); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as #4. pvd is locally allocated; pvd->ppe assignment is within the fixed-size struct.
Finding #6 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | be32_to_cpu() line 186 |
| Taint snippet | vgda_sector = be32_to_cpu(p->vgda_psn[0]); |
| Tainted var | numpps |
| Loop | for_loop line 245 |
| Sink snippet | for (i = 0; i < numpps; i += 1) { |
| Possibly guarded | no |
Dismissed: numpps is clamped to ARRAY_SIZE(pvd->ppe) at lines 242-243 before the loop. The loop index i runs from 0 to numpps-1, accessing pvd->ppe[i] which is within the allocated struct. No counterexample: numpps cannot exceed ARRAY_SIZE(pvd->ppe) due to explicit clamp.
Finding #7 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be32_to_cpu() line 186 |
| Taint snippet | vgda_sector = be32_to_cpu(p->vgda_psn[0]); |
| Tainted var | pvd |
| Pointer deref | pvd->ppe line 246 |
| Sink snippet | struct ppe *p = pvd->ppe + i; |
| Possibly guarded | no |
Dismissed: p = pvd->ppe + i where i < numpps <= ARRAY_SIZE(pvd->ppe). Both pvd and the ppe array are within the locally kmalloc'd struct pvd. Safe.
Finding #8 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be32_to_cpu() line 186 |
| Taint snippet | vgda_sector = be32_to_cpu(p->vgda_psn[0]); |
| Tainted var | p |
| Pointer deref | p->lp_ix line 249 |
| Sink snippet | lp_ix = be16_to_cpu(p->lp_ix); |
| Possibly guarded | no |
Dismissed: p points into pvd->ppe[i] which is within the locally allocated struct pvd of known size. p->lp_ix is a field access within a locally kmalloc'd struct. The taint chain through vgda_sector -> alloc_pvd -> pvd -> ppe+i -> p->lp_ix is all within the kernel-allocated struct.
Finding #9 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be32_to_cpu() line 186 |
| Taint snippet | vgda_sector = be32_to_cpu(p->vgda_psn[0]); |
| Tainted var | p |
| Pointer deref | p->lv_ix line 254 |
| Sink snippet | lv_ix = be16_to_cpu(p->lv_ix) - 1; |
| Possibly guarded | no |
Dismissed: Same as #8. p->lv_ix is within the locally allocated struct pvd. False positive.
Finding #10 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | be16_to_cpu() line 254 |
| Taint snippet | lv_ix = be16_to_cpu(p->lv_ix) - 1; |
| Tainted var | lv_ix |
| Subscript | [] line 259 |
| Sink snippet | lvip[lv_ix].pps_found += 1; |
| Possibly guarded | yes (heuristic) |
Dismissed: lv_ix = be16_to_cpu(p->lv_ix) - 1. If p->lv_ix == 0, lv_ix wraps to UINT_MAX (unsigned), which fails the check at line 255 (lv_ix >= state->limit) and continues. For any nonzero value, lv_ix is checked against state->limit before use. lvip has state->limit entries. No counterexample possible.
Finding #11 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | be16_to_cpu() line 254 |
| Taint snippet | lv_ix = be16_to_cpu(p->lv_ix) - 1; |
| Tainted var | lv_ix |
| Subscript | [] line 267 |
| Sink snippet | if (lp_ix == lvip[lv_ix].pps_per_lv) { |
| Possibly guarded | yes (heuristic) |
Dismissed: Same guard as #10 applies. lv_ix < state->limit guaranteed at this point. lvip[lv_ix] is within bounds.
Finding #12 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | be16_to_cpu() line 254 |
| Taint snippet | lv_ix = be16_to_cpu(p->lv_ix) - 1; |
| Tainted var | lv_ix |
| Subscript | [] line 270 |
| Sink snippet | lvip[lv_ix].pps_per_lv * pp_blocks_size); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same guard applies. lv_ix < state->limit at this code path. False positive.
Finding #13 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | be16_to_cpu() line 254 |
| Taint snippet | lv_ix = be16_to_cpu(p->lv_ix) - 1; |
| Tainted var | lv_ix |
| Subscript | [] line 272 |
| Sink snippet | n[lv_ix].name); |
| Possibly guarded | yes (heuristic) |
Dismissed: n[lv_ix].name: lv_ix < state->limit. n was allocated by alloc_lvn() with LVM_MAXLVS entries. If state->limit > LVM_MAXLVS, there could be an issue, but typically state->limit is bounded. The existing guard on lv_ix is necessary and appears sufficient assuming state->limit <= LVM_MAXLVS.
Finding #14 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | be16_to_cpu() line 254 |
| Taint snippet | lv_ix = be16_to_cpu(p->lv_ix) - 1; |
| Tainted var | lv_ix |
| Subscript | [] line 273 |
| Sink snippet | lvip[lv_ix].lv_is_contiguous = 1; |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as #10-#12. lv_ix < state->limit is guaranteed by the guard at line 255.
Finding #15 — Category C — cross-function via put_partition() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | be16_to_cpu() line 254 |
| Taint snippet | lv_ix = be16_to_cpu(p->lv_ix) - 1; |
| Tainted var | lv_ix |
| Call site | line 268 — passes lv_ix to put_partition() |
| Call snippet | put_partition(state, lv_ix + 1, |
| Subscript (in callee) | [] line 41 |
| Sink snippet | p->parts[n].from = from; |
| Possibly guarded | yes (heuristic) |
Dismissed: put_partition() is called with lv_ix+1. put_partition() itself checks n < p->limit before indexing p->parts[n]. Additionally, lv_ix < state->limit = p->limit, so lv_ix+1 <= state->limit, and the callee's guard (n < p->limit) ensures safety.
Finding #16 — Category C — cross-function via put_partition() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | be16_to_cpu() line 254 |
| Taint snippet | lv_ix = be16_to_cpu(p->lv_ix) - 1; |
| Tainted var | lv_ix |
| Call site | line 268 — passes lv_ix to put_partition() |
| Call snippet | put_partition(state, lv_ix + 1, |
| Subscript (in callee) | [] line 42 |
| Sink snippet | p->parts[n].size = size; |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as #15. put_partition() validates n < p->limit before both parts[n].from and parts[n].size assignments.
amiga_partition() — block/partitions/amiga.c FP confidence=high
The value 'blk' from rdb->rdb_PartitionList is indeed disk-supplied (server-supplied in the broader sense), but it does NOT control an iteration count in the traditional sense. The loop termination condition is 'part<=16' (a hard cap of 16 iterations) AND '(s32)blk>0'. Each iteration, 'blk' is updated to 'pb->pb_Next' (another disk-supplied value). The loop is essentially a linked-list traversal, not an array traversal indexed by 'blk'. The 'blk' value is used as a sector number to read from disk via read_part_sector(), which itself handles invalid sector numbers safely (returning NULL, which triggers an error return). There is no buffer whose bounds could be violated by the value of 'blk': it's passed to read_part_sector() which validates access to the disk, and if the read fails, the code returns -1. The hard cap of part<=16 prevents infinite linked-list traversal. The overflow check at line 90 (check_mul_overflow) prevents arithmetic overflow when computing the actual disk block number. This is a false positive — the scanner misidentified a disk sector address (used as an argument to a safe I/O function with NULL-check) as an unchecked loop bound.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | be32_to_cpu() line 86 |
| Taint snippet | blk = be32_to_cpu(rdb->rdb_PartitionList); |
| Tainted var | blk |
| Loop | for_loop line 88 |
| Sink snippet | for (part = 1; (s32) blk>0 && part<=16; part++, put_dev_sector(sect)) { |
| Possibly guarded | no |
Dismissed: The loop has two independent termination guards: (1) a hard cap 'part<=16' limiting to at most 16 iterations regardless of disk content, and (2) '(s32)blk>0' which stops if the disk-supplied next-block pointer is non-positive. Within each iteration, 'blk' is multiplied by 'blksize' with overflow checking (check_mul_overflow at line 90), then passed to read_part_sector() which returns NULL on failure (invalid sector) causing a clean error exit. There is no array indexing or memory access where 'blk' could cause OOB — it is only used as a disk sector address for I/O. No counterexample can be constructed where a specific value of 'blk' causes memory corruption, because read_part_sector() handles all sector addresses (valid or not) and returns NULL on error. The finding is a false positive.
alloc_read_gpt_entries() — block/partitions/efi.c FP confidence=high
alloc_read_gpt_entries() is a static function called only from is_gpt_valid(). Before the call at line 429, is_gpt_valid() validates: (1) sizeof_partition_entry == sizeof(gpt_entry) exactly (line 415), and (2) num_partition_entries * sizeof_partition_entry <= KMALLOC_MAX_SIZE (lines 421-427). These two gates together bound 'count' to a safe kmalloc size and ensure the allocation is not undersized relative to the data read. Inside read_lba(), memcpy uses 'copied' which is min(512, remaining_count), writing into a buffer of exactly 'count' bytes, so no OOB write is possible.
Finding #1 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le32_to_cpu() line 279 |
| Taint snippet | count = (size_t)le32_to_cpu(gpt->num_partition_entries) * |
| Tainted var | count |
| Sink | kmalloc() line 283 (arg 0, role=size) |
| Sink snippet | pte = kmalloc(count, GFP_KERNEL); |
| Possibly guarded | no |
Dismissed: The call site (is_gpt_valid) validates pt_size <= KMALLOC_MAX_SIZE before calling alloc_read_gpt_entries(). Since alloc_read_gpt_entries() is static and only called from this one validated call site, 'count' is bounded. No counterexample can be constructed: any value that would overflow or exceed KMALLOC_MAX_SIZE is rejected by the gate at lines 421-427.
Finding #2 — Category B — cross-function via read_lba() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le32_to_cpu() line 279 |
| Taint snippet | count = (size_t)le32_to_cpu(gpt->num_partition_entries) * |
| Tainted var | count |
| Call site | line 287 — passes count to read_lba() |
| Call snippet | if (read_lba(state, le64_to_cpu(gpt->partition_entry_lba), |
| Sink (in callee) | memcpy() line 252 (arg 2, role=size) |
| Sink snippet | memcpy(buffer, data, copied); |
| Possibly guarded | no |
Dismissed: In read_lba(), the memcpy size argument is 'copied' = min(512, remaining_count), not 'count' directly. The destination buffer 'pte' was allocated with kmalloc(count), and the loop reads exactly up to 'count' total bytes in 512-byte chunks, so no OOB write is possible. The cross-function taint path is safe because the destination is sized to 'count' and the per-iteration copy is bounded by remaining capacity.
efi_partition() — block/partitions/efi.c FP confidence=high
The GPT header is read from disk (server/storage-supplied), so num_partition_entries is externally supplied. However, find_valid_gpt() is called before the loop and its postcondition explicitly states that *gpt is a validated GPT header and *ptes is a validated array of partition entries verified against disk sector boundaries and GPT structural constraints. This validation covers the loop bound: find_valid_gpt() would have returned 0 if num_partition_entries were inconsistent with the actual allocated ptes array. Furthermore, the loop condition has a double guard: `i < le32_to_cpu(gpt->num_partition_entries) && i < state->limit-1`, where state->limit is the kernel's own limit on the number of partitions. Any valid i that satisfies both conditions is safe to index into ptes[i] (validated by find_valid_gpt) and state->parts[i+1] (bounded by state->limit-1 check). No counterexample can be constructed that passes find_valid_gpt() validation AND the state->limit guard AND still causes OOB access.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le32_to_cpu() line 727 |
| Taint snippet | for (i = 0; i < le32_to_cpu(gpt->num_partition_entries) && i < state->limit-1; i++) { |
| Tainted var | (inline) |
| Loop | for_loop line 727 |
| Sink snippet | for (i = 0; i < le32_to_cpu(gpt->num_partition_entries) && i < state->limit-1; i++) { |
| Possibly guarded | no |
Dismissed: find_valid_gpt() [called before taint source] establishes that gpt->num_partition_entries is consistent with the allocated ptes array — this is a full prior-traversal/structural validation. The additional `i < state->limit-1` guard in the loop condition independently caps iteration to the kernel's own partition table size. To construct a counterexample, num_partition_entries would need to exceed the actual number of valid ptes entries, but find_valid_gpt() rejects that case. No counterexample exists that bypasses both guards; the finding is a false positive.
find_valid_gpt() — block/partitions/efi.c FP confidence=high
find_valid_gpt() uses is_gpt_valid() as a comprehensive validator that checks GPT header integrity, CRC, LBA bounds, and partition entry array validity before returning. The function's validation discipline is sound. The scanner misidentified the flow: le64_to_cpu(pgpt->alternate_lba) is passed as a sector_t argument to is_gpt_valid(), which validates it internally; good_agpt receives only the int return value (0 or 1) of that function, not the 64-bit sector value. No actual truncation occurs.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le64_to_cpu() line 617 |
| Taint snippet | good_agpt = is_gpt_valid(state, |
| Tainted var | good_agpt |
| Truncation | line 617: 64 → 32-bit u32 |
| Sink snippet | good_agpt = is_gpt_valid(state, |
| Possibly guarded | no |
Dismissed: The scanner incorrectly identifies good_agpt as carrying a 64-bit truncation. good_agpt is an int receiving the return value of is_gpt_valid() (always 0 or 1). The le64_to_cpu(pgpt->alternate_lba) u64 value is passed as a sector_t parameter to is_gpt_valid(), which validates it against disk bounds internally per its documented postconditions. No 64→32 bit truncation of the sector value into good_agpt actually occurs. Counterexample: no concrete values of alternate_lba can cause good_agpt to be a truncated large number, since good_agpt only ever holds 0 or 1 from is_gpt_valid()'s return.
utf16_le_to_7bit() — block/partitions/efi.c FP confidence=high
The scanner flagged a 16-bit to 8-bit truncation, but the RHS expression explicitly applies '& 0x7f' before assignment to the u8 variable. This masking limits the value to 7 bits (0–127), which trivially fits in a u8 (0–255). The scanner's own note says to flag as false positive when masking limits the value to the destination width — that condition is met here. Additionally, the 'partition_name' field comes from a GPT partition table on disk (device-supplied, not network server-supplied in the strict sense), but even treating it as untrusted input, the masking makes the truncation safe. The call site also correctly caps 'label_max' using min() of the two array sizes, preventing any OOB access.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 684 |
| Taint snippet | u8 c = le16_to_cpu(in[i]) & 0x7f; |
| Tainted var | c |
| Truncation | line 684: 16 → 8-bit u8 |
| Sink snippet | u8 c = le16_to_cpu(in[i]) & 0x7f; |
| Possibly guarded | no |
Dismissed: The expression 'le16_to_cpu(in[i]) & 0x7f' explicitly masks the 16-bit value to 7 bits before assigning to u8 c. The scanner's own guideline states this is a false positive when masking limits the value to the destination width. No counterexample exists: any 16-bit value AND-ed with 0x7f produces a value in [0, 127], which is always within u8 range. The call site uses min(ARRAY_SIZE(info->volname)-1, ARRAY_SIZE(ptes[i].partition_name)) to bound 'label_max', ensuring neither the read of in[i] nor the write to out[i] can go out of bounds.
ldm_frag_add() — block/partitions/ldm.c MIXED confidence=high
Most findings (#2-#15) are false positives: the scanner incorrectly propagated taint from the server-supplied 'num' variable through the kmalloc() return value 'f', treating all subsequent accesses to the locally-allocated struct as tainted pointer dereferences. In reality, 'f' is a kernel-allocated buffer and field accesses are writes to local memory, not reads from untrusted data. Finding #1 is a genuine integer overflow risk: 'size*num' in the kmalloc argument lacks overflow protection. Although 'num' is bounded to [1,4], 'size' (ldb->vm.vblk_size) is disk-supplied and may not be sufficiently validated, allowing size*num to wrap on 32-bit arithmetic.
Finding #1 — Category B — INTEGER OVERFLOW — BUG undersized_alloc
| Category | Cat B — integer overflow: sizeof (*f) + size*num |
|---|---|
| Taint source | get_unaligned_be16() line 1231 |
| Taint snippet | num = get_unaligned_be16(data + 0x0E); |
| Tainted var | f |
| Overflow expr | sizeof (*f) + size*num |
| Safe fix | kmalloc_array() or check_mul_overflow() |
| Sink | kmalloc() line 1247 (arg 0, role=size_mul_overflow) |
| Sink snippet | f = kmalloc (sizeof (*f) + size*num, GFP_KERNEL); |
| Possibly guarded | yes (heuristic) |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in ldm_frag_add or subsequent heap corruption if size*num wraps to a small value, causing kmalloc to return an undersized buffer
Fix: Replace 'kmalloc(sizeof(*f) + size*num, GFP_KERNEL)' with a safe form: check_mul_overflow(size, num, &total) || check_add_overflow(sizeof(*f), total, &alloc_size), or use kmalloc(struct_size(f, data, (size_t)size * num), GFP_KERNEL) with prior overflow check. Also validate 'size' against a reasonable maximum before this computation.
CVE pattern: integer overflow in kmalloc size expression leading to undersized allocation
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | get_unaligned_be16() line 1231 |
| Taint snippet | num = get_unaligned_be16(data + 0x0E); |
| Tainted var | f |
| Pointer deref | f->group line 1253 |
| Sink snippet | f->group = group; |
| Possibly guarded | yes (heuristic) |
Dismissed: False positive. 'f' is a pointer returned by kmalloc(), locally allocated. The scanner incorrectly propagated taint from 'num' (used in the allocation size) to 'f' itself. f->group is a write to kernel-allocated memory, not an access to server-supplied data.
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | get_unaligned_be16() line 1231 |
| Taint snippet | num = get_unaligned_be16(data + 0x0E); |
| Tainted var | f |
| Pointer deref | f->num line 1254 |
| Sink snippet | f->num = num; |
| Possibly guarded | yes (heuristic) |
Dismissed: False positive. Same reasoning as #2: 'f' is locally kmalloc'd; f->num is a write to local memory.
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | get_unaligned_be16() line 1231 |
| Taint snippet | num = get_unaligned_be16(data + 0x0E); |
| Tainted var | f |
| Pointer deref | f->rec line 1255 |
| Sink snippet | f->rec = rec; |
| Possibly guarded | yes (heuristic) |
Dismissed: False positive. 'f' is locally kmalloc'd; f->rec is a write to local memory.
Finding #5 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | get_unaligned_be16() line 1231 |
| Taint snippet | num = get_unaligned_be16(data + 0x0E); |
| Tainted var | f |
| Pointer deref | f->map line 1256 |
| Sink snippet | f->map = 0xFF << num; |
| Possibly guarded | yes (heuristic) |
Dismissed: False positive. 'f' is locally kmalloc'd; f->map = 0xFF << num. Note: 'num' bounded [1,4] so shift is safe (no UB). Access to f->map is to local memory.
Finding #6 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | get_unaligned_be16() line 1231 |
| Taint snippet | num = get_unaligned_be16(data + 0x0E); |
| Tainted var | f |
| Pointer deref | f->list line 1258 |
| Sink snippet | list_add_tail (&f->list, frags); |
| Possibly guarded | yes (heuristic) |
Dismissed: False positive. 'f' is locally kmalloc'd; list_add_tail operates on locally allocated memory.
Finding #7 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | get_unaligned_be16() line 1231 |
| Taint snippet | num = get_unaligned_be16(data + 0x0E); |
| Tainted var | f |
| Pointer deref | f->num line 1260 |
| Sink snippet | if (rec >= f->num) { |
| Possibly guarded | yes (heuristic) |
Dismissed: False positive. At the 'found:' label, 'f' may come from a list traversal (existing frag) or from kmalloc. In either case it's a kernel-managed struct. f->num is a read from a previously kernel-written field.
Finding #8 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | get_unaligned_be16() line 1231 |
| Taint snippet | num = get_unaligned_be16(data + 0x0E); |
| Tainted var | f |
| Pointer deref | f->num line 1261 |
| Sink snippet | ldm_error("REC value (%d) exceeds NUM value (%d)", rec, f->num); |
| Possibly guarded | yes (heuristic) |
Dismissed: False positive. Same as #7; f->num in the error message is a read of a kernel-written field.
Finding #9 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | get_unaligned_be16() line 1231 |
| Taint snippet | num = get_unaligned_be16(data + 0x0E); |
| Tainted var | f |
| Pointer deref | f->map line 1264 |
| Sink snippet | if (f->map & (1 << rec)) { |
| Possibly guarded | yes (heuristic) |
Dismissed: False positive. f->map is a kernel-written field; the access is to local/kernel-managed memory.
Finding #10 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | get_unaligned_be16() line 1231 |
| Taint snippet | num = get_unaligned_be16(data + 0x0E); |
| Tainted var | f |
| Pointer deref | f->map line 1266 |
| Sink snippet | f->map &= 0x7F; /* Mark the group as broken */ |
| Possibly guarded | yes (heuristic) |
Dismissed: False positive. f->map &= 0x7F is a write to kernel-allocated memory.
Finding #11 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | get_unaligned_be16() line 1231 |
| Taint snippet | num = get_unaligned_be16(data + 0x0E); |
| Tainted var | f |
| Pointer deref | f->map line 1269 |
| Sink snippet | f->map |= (1 << rec); |
| Possibly guarded | yes (heuristic) |
Dismissed: False positive. f->map |= (1 << rec) is a write to kernel-allocated memory. 'rec' is bounded by rec < num <= 4 so the shift is safe.
Finding #12 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | get_unaligned_be16() line 1231 |
| Taint snippet | num = get_unaligned_be16(data + 0x0E); |
| Tainted var | f |
| Sink | memcpy() line 1271 (arg 0, role=pointer) |
| Sink snippet | memcpy(f->data, data, VBLK_SIZE_HEAD); |
| Possibly guarded | yes (heuristic) |
Dismissed: False positive. f->data is the flexible array member of the locally kmalloc'd struct. The buffer was allocated with sizeof(*f)+size*num bytes. VBLK_SIZE_HEAD fits within that allocation (size >= 2*VBLK_SIZE_HEAD per the guard at line 1224). The destination is kernel-allocated memory.
Finding #13 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | get_unaligned_be16() line 1231 |
| Taint snippet | num = get_unaligned_be16(data + 0x0E); |
| Tainted var | f |
| Pointer deref | f->data line 1271 |
| Sink snippet | memcpy(f->data, data, VBLK_SIZE_HEAD); |
| Possibly guarded | yes (heuristic) |
Dismissed: False positive. Same as #12; f->data dereference is to locally allocated memory.
Finding #14 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | get_unaligned_be16() line 1231 |
| Taint snippet | num = get_unaligned_be16(data + 0x0E); |
| Tainted var | f |
| Sink | memcpy() line 1274 (arg 0, role=pointer) |
| Sink snippet | memcpy(f->data + VBLK_SIZE_HEAD + rec * size, data, size); |
| Possibly guarded | yes (heuristic) |
Dismissed: False positive. The destination f->data + VBLK_SIZE_HEAD + rec*size: rec < num and the buffer is sized sizeof(*f)+size*num, so rec*size < num*size fits within the allocation. The source pointer is kernel-allocated memory.
Finding #15 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | get_unaligned_be16() line 1231 |
| Taint snippet | num = get_unaligned_be16(data + 0x0E); |
| Tainted var | f |
| Pointer deref | f->data line 1274 |
| Sink snippet | memcpy(f->data + VBLK_SIZE_HEAD + rec * size, data, size); |
| Possibly guarded | yes (heuristic) |
Dismissed: False positive. Same as #14; f->data is locally allocated memory, not a server-supplied pointer.
mac_partition() — block/partitions/mac.c FP confidence=high
The function has careful validation that is sufficient. secsize must be a power of 2 (enforced at line 64). If secsize < 512, datasize=round_down(secsize,512)=0, and the check at line 71 always fails (returning -1), so the loop is never reached. If secsize >= 512, then secsize is a multiple of 512 (power of 2 ≥ 512), so pos%512 = (slot*secsize)%512 = 0 always, meaning part always points to data+0, well within any sector. blocks_in_map is validated against DISK_MAX_PARTS and state->limit. The 'l' variable from strnlen is bounded by sizeof(part->name), making the inner loop safe. | The function validates secsize as a power-of-2, and the check at line 71 (partoffset + sizeof(*part) > datasize) rejects secsize < 512 before the loop. For secsize >= 512 (power of 2), pos%512 is always 0, so 'part' always points to data+0, well within the sector buffer. The struct mac_partition fields (including ->name) are thus safely accessible.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 54 |
| Taint snippet | secsize = be16_to_cpu(md->block_size); |
| Tainted var | part |
| Pointer deref | part->signature line 76 |
| Sink snippet | if (be16_to_cpu(part->signature) != MAC_PARTITION_MAGIC) { |
| Possibly guarded | yes (heuristic) |
Dismissed: Line 71 checks partoffset + sizeof(*part) <= datasize before creating the part pointer. No counterexample possible: if secsize < 512, datasize=0 and check fails; if secsize >= 512, partoffset=0 and datasize=secsize >= 512 >= sizeof(*part).
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 54 |
| Taint snippet | secsize = be16_to_cpu(md->block_size); |
| Tainted var | part |
| Pointer deref | part->map_count line 80 |
| Sink snippet | blocks_in_map = be32_to_cpu(part->map_count); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same protection as finding #1 — the line 71 guard ensures part is safely dereferenceable.
Finding #3 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | be32_to_cpu() line 80 |
| Taint snippet | blocks_in_map = be32_to_cpu(part->map_count); |
| Tainted var | blocks_in_map |
| Loop | for_loop line 90 |
| Sink snippet | for (slot = 1; slot <= blocks_in_map; ++slot) { |
| Possibly guarded | yes (heuristic) |
Dismissed: blocks_in_map is checked: (1) against 0 and DISK_MAX_PARTS at line 81-84; (2) clamped to state->limit-1 at line 86-87. Each iteration reads a fresh sector via read_part_sector() and checks the magic number, breaking on mismatch. The loop count is properly bounded.
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 54 |
| Taint snippet | secsize = be16_to_cpu(md->block_size); |
| Tainted var | part |
| Pointer deref | part->signature line 97 |
| Sink snippet | if (be16_to_cpu(part->signature) != MAC_PARTITION_MAGIC) |
| Possibly guarded | yes (heuristic) |
Dismissed: In the loop: pos = slot*secsize; secsize is power-of-2 >= 512 (guaranteed by line 71 gate), so pos%512 = 0 always. part = data + 0, which fits within the 512-byte sector returned by read_part_sector. No counterexample possible.
Finding #5 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 54 |
| Taint snippet | secsize = be16_to_cpu(md->block_size); |
| Tainted var | part |
| Pointer deref | part->start_block line 100 |
| Sink snippet | be32_to_cpu(part->start_block) * (secsize/512), |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #4 — part is always at data+0 with secsize a multiple of 512, so all struct fields are within the sector.
Finding #6 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 54 |
| Taint snippet | secsize = be16_to_cpu(md->block_size); |
| Tainted var | part |
| Pointer deref | part->block_count line 101 |
| Sink snippet | be32_to_cpu(part->block_count) * (secsize/512)); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #4.
Finding #7 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 54 |
| Taint snippet | secsize = be16_to_cpu(md->block_size); |
| Tainted var | part |
| Pointer deref | part->type line 103 |
| Sink snippet | if (!strncasecmp(part->type, "Linux_RAID", 10)) |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #4.
Finding #8 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 54 |
| Taint snippet | secsize = be16_to_cpu(md->block_size); |
| Tainted var | part |
| Pointer deref | part->processor line 113 |
| Sink snippet | mac_fix_string(part->processor, 16); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #4. Additionally these accesses are under CONFIG_PPC_PMAC and only within the loop after magic check.
Finding #9 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 54 |
| Taint snippet | secsize = be16_to_cpu(md->block_size); |
| Tainted var | part |
| Pointer deref | part->name line 114 |
| Sink snippet | mac_fix_string(part->name, 32); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #4.
Finding #10 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 54 |
| Taint snippet | secsize = be16_to_cpu(md->block_size); |
| Tainted var | part |
| Pointer deref | part->type line 115 |
| Sink snippet | mac_fix_string(part->type, 32); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #4.
Finding #11 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 54 |
| Taint snippet | secsize = be16_to_cpu(md->block_size); |
| Tainted var | part |
| Pointer deref | part->status line 117 |
| Sink snippet | if ((be32_to_cpu(part->status) & MAC_STATUS_BOOTABLE) |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #4.
Finding #12 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 54 |
| Taint snippet | secsize = be16_to_cpu(md->block_size); |
| Tainted var | part |
| Pointer deref | part->processor line 118 |
| Sink snippet | && strcasecmp(part->processor, "powerpc") == 0) |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #4.
Finding #13 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 54 |
| Taint snippet | secsize = be16_to_cpu(md->block_size); |
| Tainted var | part |
| Pointer deref | part->type line 121 |
| Sink snippet | if (strcasecmp(part->type, "Apple_UNIX_SVR2") == 0 |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #4.
Finding #14 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 54 |
| Taint snippet | secsize = be16_to_cpu(md->block_size); |
| Tainted var | part |
| Pointer deref | part->type line 122 |
| Sink snippet | || (strncasecmp(part->type, "Linux", 5) == 0 |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #4.
Finding #15 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 54 |
| Taint snippet | secsize = be16_to_cpu(md->block_size); |
| Tainted var | part |
| Pointer deref | part->type line 123 |
| Sink snippet | && strcasecmp(part->type, "Linux_swap") != 0)) { |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #4.
Finding #16 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 54 |
| Taint snippet | secsize = be16_to_cpu(md->block_size); |
| Tainted var | part |
| Pointer deref | part->name line 127 |
| Sink snippet | l = strnlen(part->name, sizeof(part->name)); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #4. strnlen with sizeof(part->name) bound keeps access within the struct.
Finding #17 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | be16_to_cpu() line 54 |
| Taint snippet | secsize = be16_to_cpu(md->block_size); |
| Tainted var | part |
| Sink | strncmp() line 128 (arg 2, role=size) |
| Sink snippet | if (strncmp(part->name, "/", sizeof(part->name)) == 0) |
| Possibly guarded | yes (heuristic) |
Dismissed: sizeof(part->name) is a compile-time constant, not derived from secsize. The size argument to strncmp is sizeof(part->name), which is completely safe — it bounds both the read of part->name and the comparison length. The scanner incorrectly traced secsize taint through to this sizeof expression.
Finding #18 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 54 |
| Taint snippet | secsize = be16_to_cpu(md->block_size); |
| Tainted var | part |
| Pointer deref | part->name line 128 |
| Sink snippet | if (strncmp(part->name, "/", sizeof(part->name)) == 0) |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #4 for the pointer safety. The strncmp uses sizeof(part->name) as limit.
Finding #19 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | be16_to_cpu() line 54 |
| Taint snippet | secsize = be16_to_cpu(md->block_size); |
| Tainted var | l |
| Loop | for_loop line 130 |
| Sink snippet | for (i = 0; i <= l - 4; ++i) { |
| Possibly guarded | no |
Dismissed: l = strnlen(part->name, sizeof(part->name)). The result l is bounded by sizeof(part->name) — a compile-time constant — regardless of disk content. The loop 'i <= l-4' thus iterates at most sizeof(part->name)-4 times, always accessing within part->name's fixed-size array. No OOB possible.
Finding #20 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 54 |
| Taint snippet | secsize = be16_to_cpu(md->block_size); |
| Tainted var | part |
| Pointer deref | part->name line 131 |
| Sink snippet | if (strncasecmp(part->name + i, "root", |
| Possibly guarded | yes (heuristic) |
Dismissed: part->name + i where i <= l-4 and l <= sizeof(part->name), so i+4 <= sizeof(part->name). The access strncasecmp(part->name+i, "root", 4) reads exactly 4 bytes within the name array bounds. No OOB possible.
Finding #21 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 54 |
| Taint snippet | secsize = be16_to_cpu(md->block_size); |
| Tainted var | part |
| Pointer deref | part->name line 137 |
| Sink snippet | if (strncasecmp(part->name, "swap", 4) == 0) |
| Possibly guarded | yes (heuristic) |
Dismissed: secsize must be a power of 2 (line 64 check). The early return at lines 71-74 rejects secsize < 512 (since datasize = round_down(secsize,512) = 0 for secsize < 512, making partoffset+sizeof(*part) > 0 true). For secsize >= 512, pos = slot*secsize is always a multiple of 512, so pos%512 == 0 and part = data with no offset, safely within the page buffer. No counterexample exists that passes all guards yet causes OOB.
parse_bsd() — block/partitions/msdos.c FP confidence=high
parse_bsd() receives 'max_partitions' from callers always set to BSD_MAXPARTITIONS. Line 364-365 only reduces max_partitions (never increases it) to min(BSD_MAXPARTITIONS, d_npartitions). Since d_partitions[] is a fixed array of BSD_MAXPARTITIONS elements in struct bsd_disklabel, the loop bound is always within the array. The d_magic check at line 357 further validates the disk label structure before use.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le16_to_cpu() line 365 |
| Taint snippet | max_partitions = le16_to_cpu(l->d_npartitions); |
| Tainted var | max_partitions |
| Loop | for_loop line 366 |
| Sink snippet | for (p = l->d_partitions; p - l->d_partitions < max_partitions; p++) { |
| Possibly guarded | no |
Dismissed: All three call sites pass BSD_MAXPARTITIONS as max_partitions. Line 364-365 sets max_partitions = min(max_partitions, d_npartitions), which can only equal or decrease the value — never increase it beyond BSD_MAXPARTITIONS. The d_partitions array in struct bsd_disklabel is statically sized BSD_MAXPARTITIONS elements, so the loop bound max_partitions <= BSD_MAXPARTITIONS is always within array bounds. No counterexample can be constructed: any value of d_npartitions either reduces max_partitions (safe) or leaves it at BSD_MAXPARTITIONS (exactly the array bound). False positive.
parse_solaris_x86() — block/partitions/msdos.c FP confidence=high
The function reads v_nparts from a disk sector (server/disk-supplied), but immediately clamps it through a ternary expression to either SOLARIS_X86_NUMSLICE (16) or 8 — both compile-time constants. The loop bound max_nparts is therefore always one of two known safe values, and v_slice[] has SOLARIS_X86_NUMSLICE entries. No OOB access is possible. The taint tracker flagged le16_to_cpu() as a taint source but did not model the ternary sanitisation that eliminates all server-supplied influence on the loop bound.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le16_to_cpu() line 275 |
| Taint snippet | max_nparts = le16_to_cpu(v->v_nparts) > 8 ? SOLARIS_X86_NUMSLICE : 8; |
| Tainted var | max_nparts |
| Loop | for_loop line 276 |
| Sink snippet | for (i = 0; i < max_nparts && state->next < state->limit; i++) { |
| Possibly guarded | no |
Dismissed: max_nparts = le16_to_cpu(v->v_nparts) > 8 ? SOLARIS_X86_NUMSLICE : 8 clamps max_nparts to either 16 or 8, both compile-time constants. The v_slice array has SOLARIS_X86_NUMSLICE (16) elements, so i in [0, max_nparts-1] is always within bounds. No counterexample exists: any server-supplied v->v_nparts value, no matter how large, results in max_nparts being at most 16, which is exactly the array size. The taint propagation through the ternary is a false positive from the scanner not modelling the sanitisation.
osf_partition() — block/partitions/osf.c FP confidence=high
The function reads a disk label from a sector buffer and validates d_npartitions against MAX_OSF_PARTITIONS before the loop. The loop iterates at most MAX_OSF_PARTITIONS times, and the d_partitions array in the struct is declared with exactly MAX_OSF_PARTITIONS elements. Additionally, the loop has a secondary guard 'if (slot == state->limit) break' that caps iteration. The check at line 71 (npartitions > MAX_OSF_PARTITIONS → return 0) is the key bound, and since the partition array is sized to MAX_OSF_PARTITIONS, no out-of-bounds access is possible.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le16_to_cpu() line 70 |
| Taint snippet | npartitions = le16_to_cpu(label->d_npartitions); |
| Tainted var | npartitions |
| Loop | for_loop line 75 |
| Sink snippet | for (i = 0 ; i < npartitions; i++, partition++) { |
| Possibly guarded | yes (heuristic) |
Dismissed: npartitions is capped at MAX_OSF_PARTITIONS (line 71-74) before the loop. The d_partitions array embedded in the disklabel struct is declared as d_partitions[MAX_OSF_PARTITIONS], so the loop can iterate at most MAX_OSF_PARTITIONS times, staying within the statically-sized array. No counterexample exists: any value of npartitions > MAX_OSF_PARTITIONS causes an early return, and values ≤ MAX_OSF_PARTITIONS keep 'partition' within the array bounds. The finding is a false positive.
sun_partition() — block/partitions/sun.c FP confidence=high
The function reads a disk partition label (external data) and validates nparts <= 8 as a precondition for use_vtoc. The ternary at line 97 only uses the disk-supplied nparts value when use_vtoc is true, which requires nparts <= 8. Otherwise nparts is the constant 8. In both cases nparts <= 8, keeping the loop within the 8-element partitions[] and infos[] arrays.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | be16_to_cpu() line 97 |
| Taint snippet | nparts = (use_vtoc) ? be16_to_cpu(label->vtoc.nparts) : 8; |
| Tainted var | nparts |
| Loop | for_loop line 106 |
| Sink snippet | for (i = 0; i < nparts; i++, p++) { |
| Possibly guarded | no |
Dismissed: No counterexample exists: use_vtoc is only true when nparts <= 8 (line 94), so the ternary at line 97 never assigns a disk-supplied value > 8 to nparts. When use_vtoc is false, nparts is the constant 8. The loop bound is always <= 8, safely within the 8-element partitions[] and vtoc.infos[] arrays.
sysv68_partition() — block/partitions/sysv68.c FP confidence=high
The scanner misidentifies the loop iteration taint source. 'i' from be32_to_cpu(ios_slcblk) is used as a sector number for read_part_sector(), then immediately reused as a for-loop counter variable initialized to 0 in 'for (i = 0; i < slices; i++, slice++)'. The actual loop bound is 'slices' from ios_slccnt (line 67), which is separately tainted and genuinely unvalidated against the sector buffer size (512 bytes / sizeof(struct slice)). However, the flagged finding #1 specifically attributes the loop iteration taint to 'i' from ios_slcblk, which is incorrect — 'i' is reset to 0 in the for-loop initializer and does not control the iteration count. A real bug may exist regarding 'slices' being unbounded relative to the sector buffer, but that is not what was flagged.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | be32_to_cpu() line 68 |
| Taint snippet | i = be32_to_cpu(b->dk_ios.ios_slcblk); |
| Tainted var | i |
| Loop | for_loop line 78 |
| Sink snippet | for (i = 0; i < slices; i++, slice++) { |
| Possibly guarded | no |
Dismissed: The scanner incorrectly attributes loop iteration control to 'i' from ios_slcblk. That 'i' is used as a sector number in read_part_sector() then reinitialized to 0 in the for-loop header. The for-loop iteration count is actually controlled by 'slices' (from ios_slccnt, line 67), which is a separate tainted value. No counterexample exists for 'i' from ios_slcblk controlling the loop count, because 'i' is overwritten by the for-loop initializer. The finding as stated is a false positive. Note: there is a separate potential real issue where 'slices' is not validated against the 512-byte sector buffer size before the loop increments 'slice' past the buffer end, but that is not what was flagged here.
opal_discovery0_end() — block/sed-opal.c FP confidence=high
The function properly validates hlen against IO_BUFFER_LENGTH before use. len_out is further capped by min_t(u64, discv_out->size, hlen), bounding both the source read (hlen <= IO_BUFFER_LENGTH - sizeof(*hdr)) and the destination write (len_out <= discv_out->size, user's stated buffer capacity). copy_to_user with a user-supplied size is normal kernel practice; the MMU protects against invalid user pointers. No counterexample can be constructed that passes the line-585 guard yet causes OOB.
Finding #1 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | be32_to_cpu() line 580 |
| Taint snippet | u32 hlen = be32_to_cpu(hdr->length); |
| Tainted var | len_out |
| Sink | copy_to_user() line 594 (arg 2, role=size) |
| Sink snippet | if (buf_out && copy_to_user(buf_out, dev->resp, len_out)) |
| Possibly guarded | no |
Dismissed: hlen is checked at line 585 against IO_BUFFER_LENGTH (source buffer bound). len_out = min_t(u64, discv_out->size, hlen) caps the copy at both user's claimed buffer size and the validated hlen. No OOB read of dev->resp is possible because len_out <= hlen <= IO_BUFFER_LENGTH - sizeof(*hdr) < IO_BUFFER_LENGTH. No counterexample can be constructed.
Finding #2 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 594 |
| Taint snippet | if (buf_out && copy_to_user(buf_out, dev->resp, len_out)) |
| Tainted var | len_out |
| Unvalidated size | copy_to_user() arg 2 line 594 — size len_out |
| Sink snippet | if (buf_out && copy_to_user(buf_out, dev->resp, len_out)) |
| Possibly guarded | no |
Dismissed: len_out is structurally bounded by min(discv_out->size, hlen). The copy_to_user size argument never exceeds the user's declared buffer capacity (discv_out->size), which is standard practice. The scanner flagged this because discv_out->size is user-supplied, but using the user's own declared size as the copy limit is correct and safe.
response_parse() — block/sed-opal.c FP confidence=medium
The pre-loop check validates slen <= IO_BUFFER_LENGTH - sizeof(*hdr), bounding total iterations. Within the loop, if token_length exceeds remaining total, total goes negative and the while(total > 0) guard exits before the next pos[0] access — preventing OOB in the loop control itself. Potential issues may exist inside the sub-parsers (response_parse_short etc.) reading beyond their bounds, but that is not what this finding flags.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | be32_to_cpu() line 1036 |
| Taint snippet | slen = be32_to_cpu(hdr->subpkt.length); |
| Tainted var | total |
| Loop | while_loop line 1054 |
| Sink snippet | while (total > 0) { |
| Possibly guarded | no |
Dismissed: The check at line 1040-1046 validates slen <= IO_BUFFER_LENGTH - sizeof(*hdr). The loop uses total=slen and while(total > 0). If a sub-parser returns a token_length > total, total goes negative (terminating the loop) before the next pos[0] is read. No counterexample found where the loop itself causes OOB access at pos[0] — the guard is sufficient for iteration control. Sub-parser internal reads could be a separate concern not covered by this finding.