Contents
fs/smb/client/ (67 functions) — 4 real, 253 FP
open_cached_dir() — cached_dir.c FP
build_sec_desc() — cifsacl.c FP
id_mode_to_cifs_acl() — cifsacl.c FP
parse_dacl() — cifsacl.c FP
parse_sec_desc() — cifsacl.c FP
replace_sids_and_copy_aces() — cifsacl.c FP
set_chmod_dacl() — cifsacl.c FP
validate_dacl() — cifsacl.c FP
CIFSFindFirst() — cifssmb.c FP
CIFSFindNext() — cifssmb.c FP
CIFSGetExtAttr() — cifssmb.c FP
CIFSGetSrvInodeNumber() — cifssmb.c FP
CIFSPOSIXCreate() — cifssmb.c FP
CIFSSMBPosixLock() — cifssmb.c FP
CIFSSMBQAllEAs() — cifssmb.c FP
CIFSSMBQFSAttributeInfo() — cifssmb.c FP
CIFSSMBQFSDeviceInfo() — cifssmb.c FP
CIFSSMBQFSInfo() — cifssmb.c FP
CIFSSMBQFSPosixInfo() — cifssmb.c FP
CIFSSMBQFSUnixInfo() — cifssmb.c FP
CIFSSMBRead() — cifssmb.c FP
CIFSSMBUnixQuerySymLink() — cifssmb.c MIXED
SMBOldQFSInfo() — cifssmb.c FP
cifs_create_reparse_inode() — cifssmb.c BUG
cifs_do_get_acl() — cifssmb.c FP
cifs_query_reparse_point() — cifssmb.c FP
cifs_to_posix_acl() — cifssmb.c FP
parse_dfs_referrals() — misc.c FP
cnvrtDosUnixTm() — netmisc.c FP
mknod_wsl() — reparse.c FP
parse_reparse_native_symlink() — reparse.c FP
parse_reparse_nfs() — reparse.c FP
parse_reparse_wsl_symlink() — reparse.c FP
map_smb_to_linux_error() — smb1maperror.c FP
is_valid_oplock_break() — smb1misc.c FP
cifs_query_path_info() — smb1ops.c FP
coalesce_t2() — smb1transport.c FP
smb2_parse_symlink_response() — smb2file.c FP
symlink_data() — smb2file.c FP
check_wsl_eas() — smb2inode.c FP
parse_posix_sids() — smb2inode.c FP
reparse_buf_ptr() — smb2inode.c FP
smb2_compound_op() — smb2inode.c FP
__smb2_calc_size() — smb2misc.c FP
smb2_check_message() — smb2misc.c FP
smb2_tcon_find_pending_open_lease() — smb2misc.c FP
smb2_tcon_has_lease() — smb2misc.c FP
crypt_message() — smb2ops.c FP
move_smb2_ea_to_cifs() — smb2ops.c FP
parse_server_interfaces() — smb2ops.c FP
receive_encrypted_standard() — smb2ops.c FP
smb2_query_eas() — smb2ops.c FP
smb3_fiemap() — smb2ops.c FP
smb3_simple_fallocate_range() — smb2ops.c FP
SMB2_QFS_attr() — smb2pdu.c FP
__smb2_plain_req_init() — smb2pdu.c FP
decode_compress_ctx() — smb2pdu.c FP
fill_small_buf() — smb2pdu.c FP
parse_posix_ctxt() — smb2pdu.c FP
posix_info_parse() — smb2pdu.c FP
query_info() — smb2pdu.c FP
smb2_parse_contexts() — smb2pdu.c FP
smb311_decode_neg_context() — smb2pdu.c FP
smb2_calc_signature() — smb2transport.c FP
smb2_seq_num_into_buf() — smb2transport.c FP
smb3_calc_signature() — smb2transport.c FP
__release_mid() — transport.c FP
Summary
| Function | File | Assessment | Confidence | Real | FP | Unanalyzed |
|---|---|---|---|---|---|---|
| fs/smb/client/ — 67 functions, 4 real, 253 FP | ||||||
| open_cached_dir() | fs/smb/client/cached_dir.c | FP | high | 0 | 1 | 0 |
| build_sec_desc() | fs/smb/client/cifsacl.c | FP | high | 0 | 22 | 0 |
| id_mode_to_cifs_acl() | fs/smb/client/cifsacl.c | FP | high | 0 | 8 | 0 |
| parse_dacl() | fs/smb/client/cifsacl.c | FP | high | 0 | 3 | 0 |
| parse_sec_desc() | fs/smb/client/cifsacl.c | FP | high | 0 | 1 | 0 |
| replace_sids_and_copy_aces() | fs/smb/client/cifsacl.c | FP | medium | 0 | 1 | 0 |
| set_chmod_dacl() | fs/smb/client/cifsacl.c | FP | medium | 0 | 1 | 0 |
| validate_dacl() | fs/smb/client/cifsacl.c | FP | high | 0 | 1 | 0 |
| CIFSFindFirst() | fs/smb/client/cifssmb.c | FP | high | 0 | 4 | 0 |
| CIFSFindNext() | fs/smb/client/cifssmb.c | FP | medium | 0 | 3 | 0 |
| CIFSGetExtAttr() | fs/smb/client/cifssmb.c | FP | high | 0 | 2 | 0 |
| CIFSGetSrvInodeNumber() | fs/smb/client/cifssmb.c | FP | medium | 0 | 1 | 0 |
| CIFSPOSIXCreate() | fs/smb/client/cifssmb.c | FP | medium | 0 | 5 | 0 |
| CIFSSMBPosixLock() | fs/smb/client/cifssmb.c | FP | high | 0 | 7 | 0 |
| CIFSSMBQAllEAs() | fs/smb/client/cifssmb.c | FP | high | 0 | 11 | 0 |
| CIFSSMBQFSAttributeInfo() | fs/smb/client/cifssmb.c | FP | medium | 0 | 1 | 0 |
| CIFSSMBQFSDeviceInfo() | fs/smb/client/cifssmb.c | FP | medium | 0 | 1 | 0 |
| CIFSSMBQFSInfo() | fs/smb/client/cifssmb.c | FP | high | 0 | 4 | 0 |
| CIFSSMBQFSPosixInfo() | fs/smb/client/cifssmb.c | FP | high | 0 | 9 | 0 |
| CIFSSMBQFSUnixInfo() | fs/smb/client/cifssmb.c | FP | high | 0 | 1 | 0 |
| CIFSSMBRead() | fs/smb/client/cifssmb.c | FP | high | 0 | 2 | 0 |
| CIFSSMBUnixQuerySymLink() | fs/smb/client/cifssmb.c | MIXED | medium | 2 | 0 | 0 |
| SMBOldQFSInfo() | fs/smb/client/cifssmb.c | FP | medium | 0 | 4 | 0 |
| cifs_create_reparse_inode() | fs/smb/client/cifssmb.c | BUG | medium | 2 | 0 | 0 |
| cifs_do_get_acl() | fs/smb/client/cifssmb.c | FP | high | 0 | 2 | 0 |
| cifs_query_reparse_point() | fs/smb/client/cifssmb.c | FP | high | 0 | 3 | 0 |
| cifs_to_posix_acl() | fs/smb/client/cifssmb.c | FP | high | 0 | 1 | 0 |
| parse_dfs_referrals() | fs/smb/client/misc.c | FP | high | 0 | 3 | 0 |
| cnvrtDosUnixTm() | fs/smb/client/netmisc.c | FP | high | 0 | 1 | 0 |
| mknod_wsl() | fs/smb/client/reparse.c | FP | high | 0 | 2 | 0 |
| parse_reparse_native_symlink() | fs/smb/client/reparse.c | FP | high | 0 | 10 | 0 |
| parse_reparse_nfs() | fs/smb/client/reparse.c | FP | high | 0 | 1 | 0 |
| parse_reparse_wsl_symlink() | fs/smb/client/reparse.c | FP | high | 0 | 3 | 0 |
| map_smb_to_linux_error() | fs/smb/client/smb1maperror.c | FP | high | 0 | 2 | 0 |
| is_valid_oplock_break() | fs/smb/client/smb1misc.c | FP | medium | 0 | 1 | 0 |
| cifs_query_path_info() | fs/smb/client/smb1ops.c | FP | high | 0 | 27 | 0 |
| coalesce_t2() | fs/smb/client/smb1transport.c | FP | high | 0 | 3 | 0 |
| smb2_parse_symlink_response() | fs/smb/client/smb2file.c | FP | high | 0 | 10 | 0 |
| symlink_data() | fs/smb/client/smb2file.c | FP | high | 0 | 1 | 0 |
| check_wsl_eas() | fs/smb/client/smb2inode.c | FP | high | 0 | 21 | 0 |
| parse_posix_sids() | fs/smb/client/smb2inode.c | FP | high | 0 | 4 | 0 |
| reparse_buf_ptr() | fs/smb/client/smb2inode.c | FP | high | 0 | 1 | 0 |
| smb2_compound_op() | fs/smb/client/smb2inode.c | FP | high | 0 | 1 | 0 |
| __smb2_calc_size() | fs/smb/client/smb2misc.c | FP | high | 0 | 1 | 0 |
| smb2_check_message() | fs/smb/client/smb2misc.c | FP | high | 0 | 1 | 0 |
| smb2_tcon_find_pending_open_lease() | fs/smb/client/smb2misc.c | FP | high | 0 | 1 | 0 |
| smb2_tcon_has_lease() | fs/smb/client/smb2misc.c | FP | high | 0 | 1 | 0 |
| crypt_message() | fs/smb/client/smb2ops.c | FP | high | 0 | 1 | 0 |
| move_smb2_ea_to_cifs() | fs/smb/client/smb2ops.c | FP | high | 0 | 1 | 0 |
| parse_server_interfaces() | fs/smb/client/smb2ops.c | FP | high | 0 | 1 | 0 |
| receive_encrypted_standard() | fs/smb/client/smb2ops.c | FP | medium | 0 | 1 | 0 |
| smb2_query_eas() | fs/smb/client/smb2ops.c | FP | high | 0 | 7 | 0 |
| smb3_fiemap() | fs/smb/client/smb2ops.c | FP | high | 0 | 1 | 0 |
| smb3_simple_fallocate_range() | fs/smb/client/smb2ops.c | FP | high | 0 | 1 | 0 |
| SMB2_QFS_attr() | fs/smb/client/smb2pdu.c | FP | high | 0 | 5 | 0 |
| __smb2_plain_req_init() | fs/smb/client/smb2pdu.c | FP | high | 0 | 1 | 0 |
| decode_compress_ctx() | fs/smb/client/smb2pdu.c | FP | high | 0 | 1 | 0 |
| fill_small_buf() | fs/smb/client/smb2pdu.c | FP | high | 0 | 1 | 0 |
| parse_posix_ctxt() | fs/smb/client/smb2pdu.c | FP | high | 0 | 4 | 0 |
| posix_info_parse() | fs/smb/client/smb2pdu.c | FP | high | 0 | 2 | 0 |
| query_info() | fs/smb/client/smb2pdu.c | FP | high | 0 | 1 | 0 |
| smb2_parse_contexts() | fs/smb/client/smb2pdu.c | FP | high | 0 | 9 | 0 |
| smb311_decode_neg_context() | fs/smb/client/smb2pdu.c | FP | high | 0 | 9 | 0 |
| smb2_calc_signature() | fs/smb/client/smb2transport.c | FP | high | 0 | 1 | 0 |
| smb2_seq_num_into_buf() | fs/smb/client/smb2transport.c | FP | high | 0 | 1 | 0 |
| smb3_calc_signature() | fs/smb/client/smb2transport.c | FP | high | 0 | 1 | 0 |
| __release_mid() | fs/smb/client/transport.c | FP | high | 0 | 10 | 0 |
Function Details
open_cached_dir() — fs/smb/client/cached_dir.c FP confidence=high
The flagged call passes le16_to_cpu(qi_rsp->OutputBufferOffset) directly into smb2_validate_and_copy_iov(), which is itself a validation+copy helper. The helper internally calls smb2_validate_iov(offset, buffer_length, iov, minbufsize) before performing the memcpy. The memcpy inside the callee is only reached if smb2_validate_iov returns 0, meaning the offset+buffer_length has been verified to fit within the iov buffer. The tainted offset is validated before use — this is exactly the purpose of smb2_validate_and_copy_iov().
Finding #1 — Category A — cross-function via smb2_validate_and_copy_iov() — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | le16_to_cpu() line 371 |
| Taint snippet | if (!smb2_validate_and_copy_iov( |
| Tainted var | le16_to_cpu(qi_rsp->OutputBufferOffset) |
| Call site | line 371 — passes le16_to_cpu(qi_rsp->OutputBufferOffset) to smb2_validate_and_copy_iov() |
| Call snippet | if (!smb2_validate_and_copy_iov( |
| Sink (in callee) | memcpy() line 3874 (arg 1, role=pointer) |
| Sink snippet | memcpy(data, begin_of_buf, minbufsize); |
| Possibly guarded | no |
Dismissed: qi_rsp is a server-supplied response buffer, so OutputBufferOffset is genuinely server-supplied (taint source is valid). However, smb2_validate_and_copy_iov() is explicitly a validation+copy helper: it calls smb2_validate_iov(offset, buffer_length, iov, minbufsize) before computing begin_of_buf and calling memcpy. If the offset is out-of-bounds, smb2_validate_iov returns an error and the memcpy is never reached. No counterexample exists: any offset that would make begin_of_buf point outside the iov buffer would be caught by smb2_validate_iov. The scanner flagged the memcpy inside the validator as a sink, but accesses inside a validation function are part of the validation logic, not vulnerable sinks. Additionally, there is a prior check at line 367 ensuring OutputBufferLength >= sizeof(struct smb2_file_all_info), providing an outer sanity check. This is a false positive.
build_sec_desc() — fs/smb/client/cifsacl.c FP confidence=high
build_sec_desc() has solid validation discipline. When dacloffset is non-zero, dacl_offset_valid() ensures the pointer is within bounds (at least sizeof(smb_acl) fits), and validate_dacl() fully walks all ACEs with per-element bounds checks before any consumer function is called. All uses of dacl_ptr in both the caller and callees (set_chmod_dacl, replace_sids_and_copy_aces) are protected by this prior validation. Findings inside validate_dacl() itself are validation logic, not vulnerable sinks. The truncation finding is also spurious since replace_sids_and_copy_aces returns u16. | build_sec_desc() has a thorough validation discipline: dacl_offset_valid() checks the DACL offset is within the buffer, then validate_dacl() walks every ACE in the DACL confirming each fits within end_of_acl. The call to replace_sids_and_copy_aces() is gated behind 'if (dacloffset)' which ensures both validators have passed. The postcondition of validate_dacl covers exactly the ACE accesses flagged inside replace_sids_and_copy_aces() — num_aces and per-ACE sizes/fields are all confirmed valid before the callee iterates them.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 1417 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Pointer deref | dacl_ptr->revision line 1447 |
| Sink snippet | dacloffset ? dacl_ptr->revision : cpu_to_le16(ACL_REVISION); |
| Possibly guarded | no |
Dismissed: dacl_offset_valid() at line 1419 verifies dacloffset + sizeof(struct smb_acl) <= secdesclen, so dacl_ptr->revision (a field within smb_acl) is safe. Counterexample: cannot construct one — dacl_offset_valid ensures the full smb_acl header fits.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 1417 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Pointer deref | dacl_ptr->revision line 1467 |
| Sink snippet | dacloffset ? dacl_ptr->revision : cpu_to_le16(ACL_REVISION); |
| Possibly guarded | no |
Dismissed: Same reasoning as finding #1. dacl_offset_valid() ensures sizeof(struct smb_acl) fits at dacloffset, making dacl_ptr->revision safe to read. No counterexample possible.
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 1417 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Pointer deref | dacl_ptr->num_aces line 1468 |
| Sink snippet | ndacl_ptr->num_aces = dacl_ptr ? dacl_ptr->num_aces : 0; |
| Possibly guarded | no |
Dismissed: dacl_offset_valid() ensures sizeof(struct smb_acl) fits, so dacl_ptr->num_aces is within bounds. Additionally validate_dacl() has already returned 0 confirming full structural validity. No counterexample possible.
Finding #4 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le32_to_cpu() line 1529 |
| Taint snippet | size = replace_sids_and_copy_aces(dacl_ptr, ndacl_ptr, |
| Tainted var | size |
| Truncation | line 1529: 32 → 16-bit u16 |
| Sink snippet | size = replace_sids_and_copy_aces(dacl_ptr, ndacl_ptr, |
| Possibly guarded | no |
Dismissed: replace_sids_and_copy_aces() declares its return type as __u16 (u16 nsize). Assigning to u16 size is same-width. The scanner incorrectly identified this as a 32-to-16-bit truncation. This is a false positive.
Finding #5 — Category F — cross-function via validate_dacl() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le32_to_cpu() line 1417 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Call site | line 1425 — passes dacl_ptr to validate_dacl() |
| Call snippet | rc = validate_dacl(dacl_ptr, end_of_acl); |
| Loop | for_loop line 875 |
| Sink snippet | for (i = 0; i < num_aces; ++i) { |
| Possibly guarded | yes (heuristic) |
Dismissed: validate_dacl() IS the validation function for dacl_ptr. Accesses inside it (including the loop using num_aces as bound) are the validation logic itself. validate_dacl() bounds-checks num_aces against (dacl_size - sizeof(smb_acl)) / min_ace_size before entering the loop. This is a false positive by definition.
Finding #6 — Category E — cross-function via validate_dacl() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 1417 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Call site | line 1425 — passes dacl_ptr to validate_dacl() |
| Call snippet | rc = validate_dacl(dacl_ptr, end_of_acl); |
| Pointer deref | dacl_ptr-> line 885 |
| Sink snippet | pace->sid.num_subauth == 0 || |
| Possibly guarded | yes (heuristic) |
Dismissed: Sink is inside validate_dacl() which is the validator itself. The access at line 885 is preceded by a check at line 884 verifying end_of_dacl - acl_base >= ace_hdr_size. This is validation logic, not a vulnerable sink.
Finding #7 — Category E — cross-function via validate_dacl() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 1417 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Call site | line 1425 — passes dacl_ptr to validate_dacl() |
| Call snippet | rc = validate_dacl(dacl_ptr, end_of_acl); |
| Pointer deref | dacl_ptr-> line 886 |
| Sink snippet | pace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES) { |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as #6 — pace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES is itself a bounds check inside the validator. False positive.
Finding #8 — Category E — cross-function via validate_dacl() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 1417 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Call site | line 1425 — passes dacl_ptr to validate_dacl() |
| Call snippet | rc = validate_dacl(dacl_ptr, end_of_acl); |
| Pointer deref | dacl_ptr-> line 891 |
| Sink snippet | ace_size = ace_hdr_size + sizeof(__le32) * pace->sid.num_subauth; |
| Possibly guarded | yes (heuristic) |
Dismissed: Access at line 891 inside validate_dacl() is validation logic. num_subauth is already checked to be non-zero and <= SID_MAX_SUB_AUTHORITIES at line 885-886 before this use. False positive.
Finding #9 — Category E — cross-function via validate_dacl() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 1417 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Call site | line 1425 — passes dacl_ptr to validate_dacl() |
| Call snippet | rc = validate_dacl(dacl_ptr, end_of_acl); |
| Pointer deref | dacl_ptr-> line 893 |
| Sink snippet | le16_to_cpu(pace->size) < ace_size) { |
| Possibly guarded | yes (heuristic) |
Dismissed: pace->size read at line 893 inside validate_dacl() is validation logic — it's being bounds-checked against end_of_dacl and ace_size. False positive.
Finding #10 — Category E — cross-function via validate_dacl() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 1417 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Call site | line 1425 — passes dacl_ptr to validate_dacl() |
| Call snippet | rc = validate_dacl(dacl_ptr, end_of_acl); |
| Pointer deref | dacl_ptr-> line 898 |
| Sink snippet | ace_size = le16_to_cpu(pace->size); |
| Possibly guarded | yes (heuristic) |
Dismissed: pace->size read at line 898 inside validate_dacl() occurs after line 892-895 verified it fits in end_of_dacl. This is the validator's internal logic. False positive.
Finding #11 — Category F — cross-function via set_chmod_dacl() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le32_to_cpu() line 1417 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Call site | line 1452 — passes dacl_ptr to set_chmod_dacl() |
| Call snippet | rc = set_chmod_dacl(dacl_ptr, ndacl_ptr, owner_sid_ptr, group_sid_ptr, |
| Loop | for_loop line 1266 |
| Sink snippet | for (i = 0; i < src_num_aces; ++i) { |
| Possibly guarded | no |
Dismissed: set_chmod_dacl() is called at line 1452 only after validate_dacl() succeeded (line 1425-1427). validate_dacl() verifies num_aces and each ACE's size iteratively. The traversal in set_chmod_dacl() uses identical arithmetic (start at sizeof(smb_acl), advance by pace->size), so the pre-validated bounds apply. No counterexample: validate_dacl ensures all ACE accesses within the loop are in-bounds.
Finding #12 — Category E — cross-function via set_chmod_dacl() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 1417 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Call site | line 1452 — passes dacl_ptr to set_chmod_dacl() |
| Call snippet | rc = set_chmod_dacl(dacl_ptr, ndacl_ptr, owner_sid_ptr, group_sid_ptr, |
| Pointer deref | dacl_ptr-> line 1269 |
| Sink snippet | if (!new_aces_set && (pntace->flags & INHERITED_ACE)) { |
| Possibly guarded | no |
Dismissed: pntace->flags access in set_chmod_dacl() is covered by validate_dacl()'s per-ACE bounds check which verified each ACE's size >= min_ace_size (which includes the flags field). False positive.
Finding #13 — Category E — cross-function via set_chmod_dacl() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 1417 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Call site | line 1452 — passes dacl_ptr to set_chmod_dacl() |
| Call snippet | rc = set_chmod_dacl(dacl_ptr, ndacl_ptr, owner_sid_ptr, group_sid_ptr, |
| Pointer deref | dacl_ptr-> line 1280 |
| Sink snippet | if (((compare_sids(&pntace->sid, &sid_unix_NFS_mode) == 0) || |
| Possibly guarded | no |
Dismissed: pntace->sid access covered by validate_dacl() pre-validation. False positive.
Finding #14 — Category E — cross-function via set_chmod_dacl() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 1417 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Call site | line 1452 — passes dacl_ptr to set_chmod_dacl() |
| Call snippet | rc = set_chmod_dacl(dacl_ptr, ndacl_ptr, owner_sid_ptr, group_sid_ptr, |
| Pointer deref | dacl_ptr-> line 1281 |
| Sink snippet | (compare_sids(&pntace->sid, pownersid) == 0) || |
| Possibly guarded | yes (heuristic) |
Dismissed: pntace->sid access covered by validate_dacl() pre-validation. False positive.
Finding #15 — Category E — cross-function via set_chmod_dacl() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 1417 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Call site | line 1452 — passes dacl_ptr to set_chmod_dacl() |
| Call snippet | rc = set_chmod_dacl(dacl_ptr, ndacl_ptr, owner_sid_ptr, group_sid_ptr, |
| Pointer deref | dacl_ptr-> line 1282 |
| Sink snippet | (compare_sids(&pntace->sid, pgrpsid) == 0) || |
| Possibly guarded | yes (heuristic) |
Dismissed: pntace->sid access covered by validate_dacl() pre-validation. False positive.
Finding #16 — Category E — cross-function via set_chmod_dacl() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 1417 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Call site | line 1452 — passes dacl_ptr to set_chmod_dacl() |
| Call snippet | rc = set_chmod_dacl(dacl_ptr, ndacl_ptr, owner_sid_ptr, group_sid_ptr, |
| Pointer deref | dacl_ptr-> line 1283 |
| Sink snippet | (compare_sids(&pntace->sid, &sid_everyone) == 0) || |
| Possibly guarded | yes (heuristic) |
Dismissed: pntace->sid access covered by validate_dacl() pre-validation. False positive.
Finding #17 — Category E — cross-function via set_chmod_dacl() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 1417 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Call site | line 1452 — passes dacl_ptr to set_chmod_dacl() |
| Call snippet | rc = set_chmod_dacl(dacl_ptr, ndacl_ptr, owner_sid_ptr, group_sid_ptr, |
| Pointer deref | dacl_ptr-> line 1284 |
| Sink snippet | (compare_sids(&pntace->sid, &sid_authusers) == 0))) { |
| Possibly guarded | yes (heuristic) |
Dismissed: pntace->sid access covered by validate_dacl() pre-validation. False positive.
Finding #18 — Category E — cross-function via set_chmod_dacl() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 1417 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Call site | line 1452 — passes dacl_ptr to set_chmod_dacl() |
| Call snippet | rc = set_chmod_dacl(dacl_ptr, ndacl_ptr, owner_sid_ptr, group_sid_ptr, |
| Pointer deref | dacl_ptr-> line 1295 |
| Sink snippet | size += le16_to_cpu(pntace->size); |
| Possibly guarded | yes (heuristic) |
Dismissed: pntace->size read in set_chmod_dacl() at line 1295: validate_dacl() already verified each pace->size >= ace_hdr_size + subauth_count * 4, so reading it is safe. False positive.
Finding #19 — Category F — cross-function via replace_sids_and_copy_aces() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le32_to_cpu() line 1417 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Call site | line 1529 — passes dacl_ptr to replace_sids_and_copy_aces() |
| Call snippet | size = replace_sids_and_copy_aces(dacl_ptr, ndacl_ptr, |
| Loop | for_loop line 1212 |
| Sink snippet | for (i = 0; i < src_num_aces; ++i) { |
| Possibly guarded | no |
Dismissed: replace_sids_and_copy_aces() is called at line 1529 only when dacloffset is non-zero (line 1527), and validate_dacl() has already succeeded (line 1425). src_num_aces from pdacl->num_aces was validated by validate_dacl(). The traversal uses identical arithmetic. No counterexample possible. False positive.
Finding #20 — Category E — cross-function via replace_sids_and_copy_aces() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 1417 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Call site | line 1529 — passes dacl_ptr to replace_sids_and_copy_aces() |
| Call snippet | size = replace_sids_and_copy_aces(dacl_ptr, ndacl_ptr, |
| Pointer deref | dacl_ptr-> line 1216 |
| Sink snippet | if (pnownersid && compare_sids(&pntace->sid, pownersid) == 0) { |
| Possibly guarded | no |
Dismissed: pntace->sid access in replace_sids_and_copy_aces() is covered by validate_dacl()'s pre-validation which verified each ACE fits within the DACL bounds. False positive.
Finding #21 — Category E — cross-function via replace_sids_and_copy_aces() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 1417 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Call site | line 1529 — passes dacl_ptr to replace_sids_and_copy_aces() |
| Call snippet | size = replace_sids_and_copy_aces(dacl_ptr, ndacl_ptr, |
| Pointer deref | dacl_ptr-> line 1219 |
| Sink snippet | } else if (pngrpsid && compare_sids(&pntace->sid, pgrpsid) == 0) { |
| Possibly guarded | yes (heuristic) |
Dismissed: dacl_ptr is derived from server-supplied dacloffset, but is fully validated by dacl_offset_valid() and validate_dacl() before reaching replace_sids_and_copy_aces(). validate_dacl()'s postcondition explicitly covers every ACE entry including sid fields. No counterexample can be constructed: any dacl_ptr reaching line 1529 has been confirmed valid by validate_dacl(), which guarantees that iterating num_aces ACEs and accessing their sid fields stays within the validated buffer region.
Finding #22 — Category E — cross-function via replace_sids_and_copy_aces() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 1417 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Call site | line 1529 — passes dacl_ptr to replace_sids_and_copy_aces() |
| Call snippet | size = replace_sids_and_copy_aces(dacl_ptr, ndacl_ptr, |
| Pointer deref | dacl_ptr-> line 1226 |
| Sink snippet | size += le16_to_cpu(pntace->size); |
| Possibly guarded | no |
Dismissed: The pntace->size read at line 1226 is also covered by validate_dacl()'s postcondition, which validates every ACE's size field and confirms the full traversal stays within end_of_acl. The same num_aces loop bound and per-ACE size arithmetic used in replace_sids_and_copy_aces() was already verified by validate_dacl(). No counterexample exists: validate_dacl's full-traversal validation means the size accumulation in replace_sids_and_copy_aces() cannot exceed the validated buffer.
id_mode_to_cifs_acl() — fs/smb/client/cifsacl.c FP confidence=high
All eight findings are false positives. For findings #1 and #2: dacl_offset_valid() establishes that dacloffset+sizeof(struct smb_acl)<=secdesclen (making dacl_ptr safe to dereference), and validate_dacl() is called before the flagged accesses (lines 1820 and 1822) and validates both dacl_ptr->num_aces and dacl_ptr->size with proper bounds checks — returning 0 only if they are valid. For findings #3-#8: the flagged sinks are all inside validate_dacl() itself, which is the validation function. The accesses there are part of the validation logic, not vulnerable sinks.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 1803 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Pointer deref | dacl_ptr->num_aces line 1820 |
| Sink snippet | le16_to_cpu(dacl_ptr->num_aces) * sizeof(struct smb_ace); |
| Possibly guarded | no |
Dismissed: dacl_offset_valid() ensures dacloffset+sizeof(struct smb_acl)<=secdesclen, making dacl_ptr safe. validate_dacl() validates num_aces (line 859-869) and returns non-zero on failure; the caller checks rc and returns on error (line 1813-1817). Access at line 1820 is protected. No counterexample can be constructed: validate_dacl returning 0 guarantees num_aces <= (dacl_size - sizeof(smb_acl)) / min_ace_size.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 1803 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Pointer deref | dacl_ptr->size line 1822 |
| Sink snippet | nsecdesclen += le16_to_cpu(dacl_ptr->size); |
| Possibly guarded | no |
Dismissed: dacl_offset_valid() ensures the pointer is in bounds. validate_dacl() validates dacl_ptr->size (lines 852-857): it checks dacl_size >= sizeof(struct smb_acl) and end_of_acl >= (char*)pdacl + dacl_size, so dacl_ptr->size is safe after validate_dacl returns 0. No counterexample possible.
Finding #3 — Category F — cross-function via validate_dacl() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le32_to_cpu() line 1803 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Call site | line 1812 — passes dacl_ptr to validate_dacl() |
| Call snippet | rc = validate_dacl(dacl_ptr, (char *)pntsd + secdesclen); |
| Loop | for_loop line 875 |
| Sink snippet | for (i = 0; i < num_aces; ++i) { |
| Possibly guarded | yes (heuristic) |
Dismissed: The sink is inside validate_dacl() itself, which is the validation function. The loop iteration count num_aces is validated at line 866 against (dacl_size - sizeof(smb_acl)) / min_ace_size before the loop begins. Accesses inside the validator are the validation logic, not vulnerable sinks.
Finding #4 — Category E — cross-function via validate_dacl() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 1803 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Call site | line 1812 — passes dacl_ptr to validate_dacl() |
| Call snippet | rc = validate_dacl(dacl_ptr, (char *)pntsd + secdesclen); |
| Pointer deref | dacl_ptr-> line 885 |
| Sink snippet | pace->sid.num_subauth == 0 || |
| Possibly guarded | yes (heuristic) |
Dismissed: The sink is inside validate_dacl(). pace->sid.num_subauth is accessed only after line 884 checks end_of_dacl - acl_base >= ace_hdr_size, where ace_hdr_size includes the num_subauth field. This is the validation logic itself.
Finding #5 — Category E — cross-function via validate_dacl() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 1803 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Call site | line 1812 — passes dacl_ptr to validate_dacl() |
| Call snippet | rc = validate_dacl(dacl_ptr, (char *)pntsd + secdesclen); |
| Pointer deref | dacl_ptr-> line 886 |
| Sink snippet | pace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES) { |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #4: pace->sid.num_subauth is accessed after the bounds check at line 884 confirming ace_hdr_size bytes are available. This is part of the validation logic.
Finding #6 — Category E — cross-function via validate_dacl() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 1803 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Call site | line 1812 — passes dacl_ptr to validate_dacl() |
| Call snippet | rc = validate_dacl(dacl_ptr, (char *)pntsd + secdesclen); |
| Pointer deref | dacl_ptr-> line 891 |
| Sink snippet | ace_size = ace_hdr_size + sizeof(__le32) * pace->sid.num_subauth; |
| Possibly guarded | yes (heuristic) |
Dismissed: pace->sid.num_subauth at line 891 is used only after the bounds check at line 884 and after num_subauth is checked to be nonzero and <= SID_MAX_SUB_AUTHORITIES (line 885-886). This is validation logic inside the validator.
Finding #7 — Category E — cross-function via validate_dacl() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 1803 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Call site | line 1812 — passes dacl_ptr to validate_dacl() |
| Call snippet | rc = validate_dacl(dacl_ptr, (char *)pntsd + secdesclen); |
| Pointer deref | dacl_ptr-> line 893 |
| Sink snippet | le16_to_cpu(pace->size) < ace_size) { |
| Possibly guarded | yes (heuristic) |
Dismissed: pace->size at line 893 is accessed after the bounds check at line 892 verifies end_of_dacl - acl_base >= ace_size. This is part of the validation logic inside validate_dacl().
Finding #8 — Category E — cross-function via validate_dacl() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 1803 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Call site | line 1812 — passes dacl_ptr to validate_dacl() |
| Call snippet | rc = validate_dacl(dacl_ptr, (char *)pntsd + secdesclen); |
| Pointer deref | dacl_ptr-> line 898 |
| Sink snippet | ace_size = le16_to_cpu(pace->size); |
| Possibly guarded | yes (heuristic) |
Dismissed: pace->size at line 898 is accessed after line 892-896 verify it is within bounds. This is the validator's own logic extracting the validated ace_size for the next loop iteration.
parse_dacl() — fs/smb/client/cifsacl.c FP confidence=high
parse_dacl() calls validate_dacl() before reading num_aces from pdacl->num_aces. The validate_dacl() postcondition (as documented) confirms that every ACE entry — including its header, num_subauth field, and full sub-authority array — lies within the declared DACL bounds without exceeding end_of_acl. This is a prior full-traversal validation: validate_dacl() walks the same buffer using the same base pointer, the same per-ACE size field, and the same num_aces count with per-step bounds checks. Any num_aces value that would cause OOB access in the subsequent loop would have caused validate_dacl() to return non-zero (error), causing parse_dacl() to return early at line 928. Thus all three findings are false positives protected by the pre-loop full-traversal validator.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le16_to_cpu() line 942 |
| Taint snippet | num_aces = le16_to_cpu(pdacl->num_aces); |
| Tainted var | num_aces |
| Loop | for_loop line 950 |
| Sink snippet | for (i = 0; i < num_aces; ++i) { |
| Possibly guarded | yes (heuristic) |
Dismissed: validate_dacl() is called at line 927 before num_aces is read at line 942. Its postcondition covers: (a) the DACL header fits, (b) dacl_size is consistent, and (c) every ACE entry including its full sub-authority array lies within the declared DACL bounds. This is a prior full-traversal with structurally equivalent arithmetic — same base (pdacl), same per-element size field (ace->size), same iteration count (num_aces). No counterexample can be constructed: any num_aces that would overflow the buffer would have been rejected by validate_dacl(), causing early return at line 928. Finding is a false positive.
Finding #2 — Category F — cross-function via dump_ace() (read-only callee) — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le16_to_cpu() line 942 |
| Taint snippet | num_aces = le16_to_cpu(pdacl->num_aces); |
| Tainted var | ppace |
| Call site | line 954 — passes ppace to dump_ace() |
| Call snippet | dump_ace(ppace[i], |
| Loop | for_loop line 824 |
| Sink snippet | for (i = 0; i < num_subauth; ++i) { |
| Possibly guarded | no |
Dismissed: dump_ace() is only compiled in CONFIG_CIFS_DEBUG2. The taint chain here is: num_aces controls the loop index i, which controls ppace[i], which is passed to dump_ace(). Inside dump_ace(), num_subauth from pace->sid.num_subauth controls the inner loop. validate_dacl() already confirmed every ACE's full sub-authority array fits within bounds before parse_dacl()'s loop executes. Additionally, dump_ace() is a read-only debug function. No OOB access is reachable given the prior full-traversal validation. False positive.
Finding #3 — Category F — cross-function via compare_sids() (read-only callee) — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le16_to_cpu() line 942 |
| Taint snippet | num_aces = le16_to_cpu(pdacl->num_aces); |
| Tainted var | ppace |
| Call site | line 959 — passes ppace to compare_sids() |
| Call snippet | (compare_sids(&(ppace[i]->sid), |
| Loop | for_loop line 194 |
| Sink snippet | for (i = 0; i < num_subauth; ++i) { |
| Possibly guarded | yes (heuristic) |
Dismissed: compare_sids() uses ctsid->num_subauth (server-supplied from ACE SID) as a loop bound, but validate_dacl() already confirmed the full sub-authority array of every ACE SID fits within the DACL buffer bounds. The compare_sids() loop reads min(num_sat, num_saw) subauth entries, so it is bounded by the minimum of the two SIDs' num_subauth values. Since validate_dacl() confirmed all sub_auth entries for each ACE SID are within bounds, and the known SIDs (pownersid, pgrpsid, sid_unix_NFS_mode, etc.) are kernel-controlled with valid num_subauth values, no OOB read is possible. compare_sids() is also read-only. False positive.
parse_sec_desc() — fs/smb/client/cifsacl.c FP confidence=high
parse_sec_desc() has a solid validation discipline. osidoffset and gsidoffset are validated by sid_from_sd() before use. dacloffset is validated by dacl_offset_valid() before forming dacl_ptr. Inside parse_dacl(), validate_dacl() is called before the loop that uses num_aces. The taint chain from dacloffset → dacl_ptr → num_aces is fully covered by these validators.
Finding #1 — Category F — cross-function via parse_dacl() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le32_to_cpu() line 1348 |
| Taint snippet | dacloffset = le32_to_cpu(pntsd->dacloffset); |
| Tainted var | dacl_ptr |
| Call site | line 1385 — passes dacl_ptr to parse_dacl() |
| Call snippet | parse_dacl(dacl_ptr, end_of_acl, owner_sid_ptr, |
| Loop | for_loop line 950 |
| Sink snippet | for (i = 0; i < num_aces; ++i) { |
| Possibly guarded | yes (heuristic) |
Dismissed: Two layers of validation protect this path. First, dacl_offset_valid() (lines 1379-1382) checks that dacloffset >= sizeof(struct smb_ntsd) and dacloffset <= acl_len - sizeof(struct smb_acl), ensuring dacl_ptr points to a valid smb_acl header within the buffer. Second, inside parse_dacl(), validate_dacl() is called at line 927 before num_aces is extracted and used as the loop bound at line 950. validate_dacl() walks the ACE list with per-element bounds checks against end_of_acl, so any num_aces value that would cause the loop to walk outside the buffer is caught there. No concrete counterexample can be constructed: any num_aces large enough to cause OOB would fail the validate_dacl() check first.
replace_sids_and_copy_aces() — fs/smb/client/cifsacl.c FP confidence=medium
The caller build_sec_desc() calls validate_dacl(dacl_ptr, end_of_acl) before invoking replace_sids_and_copy_aces(). If validate_dacl performs a full per-ACE traversal with bounds checks (same base pointer, same per-element size field le16_to_cpu(ace->size), same iteration count num_aces), then the subsequent loop in replace_sids_and_copy_aces is protected by that prior full-traversal validation per pattern (c). The finding is therefore likely a false positive for the source buffer access. A separate concern about destination buffer (pndacl) bounds is not flagged here.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le16_to_cpu() line 1206 |
| Taint snippet | src_num_aces = le16_to_cpu(pdacl->num_aces); |
| Tainted var | src_num_aces |
| Loop | for_loop line 1212 |
| Sink snippet | for (i = 0; i < src_num_aces; ++i) { |
| Possibly guarded | no |
Dismissed: validate_dacl() is called in the caller with the same dacl_ptr and end_of_acl boundary before reaching this loop. Assuming validate_dacl performs a complete per-ACE traversal with per-step bounds checks (standard pattern in CIFS ACL parsing code), it establishes that all src_num_aces ACEs fit within the buffer. No counterexample can be constructed that passes validate_dacl yet causes OOB in replace_sids_and_copy_aces for the source buffer. The destination buffer bounds are a separate concern not covered by this finding.
set_chmod_dacl() — fs/smb/client/cifsacl.c FP confidence=medium
The caller build_sec_desc() calls validate_dacl(dacl_ptr, end_of_acl) before invoking set_chmod_dacl(). This validates the same buffer (same dacl_ptr, same num_aces, same per-ACE size arithmetic) with explicit end-of-buffer bounds, constituting a prior full-traversal that protects the loop in set_chmod_dacl(). The static function inherits this validation gate. Confidence is medium rather than high only because the body of validate_dacl() is not shown — if it does not actually walk all ACEs, the finding could be real.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le16_to_cpu() line 1263 |
| Taint snippet | src_num_aces = le16_to_cpu(pdacl->num_aces); |
| Tainted var | src_num_aces |
| Loop | for_loop line 1266 |
| Sink snippet | for (i = 0; i < src_num_aces; ++i) { |
| Possibly guarded | no |
Dismissed: validate_dacl(dacl_ptr, end_of_acl) is called at line 1425 in build_sec_desc() — the sole call site — before set_chmod_dacl() is invoked at line 1452. This function receives end_of_acl as a bound and is named to indicate full DACL validation, strongly suggesting it walks all ACEs with per-step bounds checks using the same num_aces and size fields. If validate_dacl returns success, the loop in set_chmod_dacl() over the same buffer is protected. No counterexample could be constructed that passes validate_dacl yet causes OOB in the subsequent loop, assuming validate_dacl performs structural validation of all ACEs against end_of_acl.
validate_dacl() — fs/smb/client/cifsacl.c FP confidence=high
validate_dacl() is itself the validation function. It validates num_aces with a pre-loop check (line 866) and has comprehensive per-iteration bounds checks throughout the loop body. Every pointer dereference is preceded by a bounds check against end_of_dacl. The flagged accesses ARE the validation logic, not a vulnerability.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le16_to_cpu() line 859 |
| Taint snippet | num_aces = le16_to_cpu(pdacl->num_aces); |
| Tainted var | num_aces |
| Loop | for_loop line 875 |
| Sink snippet | for (i = 0; i < num_aces; ++i) { |
| Possibly guarded | yes (heuristic) |
Dismissed: The loop is protected by two layers: (a) pre-loop check at line 866 verifies num_aces <= (dacl_size - sizeof(smb_acl)) / min_ace_size, bounding the maximum iteration count; (b) per-iteration bounds checks at lines 876-879, 884-889, 892-896, and 899-902 each return -EINVAL if any element would be out of bounds. Counterexample construction failed — no concrete num_aces value can pass all guards and still cause OOB. This is a false positive: the function is itself the validator, and the flagged uses are internal validation logic.
CIFSFindFirst() — fs/smb/client/cifssmb.c FP confidence=high
validate_t2() is called before the tainted ParameterOffset is extracted, with min_param_size=sizeof(T2_FFIRST_RSP_PARMS). Its postcondition guarantees that [param_off, param_off+sizeof(T2_FFIRST_RSP_PARMS)) lies entirely within the received frame. Since parms is derived from pSMBr->t2.ParameterOffset (param_off), all subsequent field accesses within T2_FFIRST_RSP_PARMS are safe. The lnoff check against DataCount at line 4588 further protects the last_entry pointer arithmetic.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 4576 |
| Taint snippet | parms = (T2_FFIRST_RSP_PARMS *)((char *)&pSMBr->hdr.Protocol + |
| Tainted var | parms |
| Pointer deref | parms->EndofSearch line 4578 |
| Sink snippet | psrch_inf->endOfSearch = !!parms->EndofSearch; |
| Possibly guarded | no |
Dismissed: validate_t2() called with min_param_size=sizeof(T2_FFIRST_RSP_PARMS) before ParameterOffset is read. This guarantees parms->EndofSearch is within the frame. No counterexample exists: any ParameterOffset that would place parms->EndofSearch out of bounds would cause validate_t2() to return an error and execution would return before reaching line 4578.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 4576 |
| Taint snippet | parms = (T2_FFIRST_RSP_PARMS *)((char *)&pSMBr->hdr.Protocol + |
| Tainted var | parms |
| Pointer deref | parms->SearchCount line 4580 |
| Sink snippet | psrch_inf->entries_in_buffer = le16_to_cpu(parms->SearchCount); |
| Possibly guarded | no |
Dismissed: Same reasoning as finding #1. validate_t2() with min_param_size=sizeof(T2_FFIRST_RSP_PARMS) covers parms->SearchCount which is within the struct. No counterexample exists.
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 4576 |
| Taint snippet | parms = (T2_FFIRST_RSP_PARMS *)((char *)&pSMBr->hdr.Protocol + |
| Tainted var | parms |
| Pointer deref | parms->LastNameOffset line 4583 |
| Sink snippet | lnoff = le16_to_cpu(parms->LastNameOffset); |
| Possibly guarded | no |
Dismissed: Same reasoning. parms->LastNameOffset is within T2_FFIRST_RSP_PARMS, covered by validate_t2()'s parameter-range check. No counterexample exists.
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 4576 |
| Taint snippet | parms = (T2_FFIRST_RSP_PARMS *)((char *)&pSMBr->hdr.Protocol + |
| Tainted var | parms |
| Pointer deref | parms->SearchHandle line 4586 |
| Sink snippet | *pnetfid = parms->SearchHandle; |
| Possibly guarded | no |
Dismissed: Same reasoning. parms->SearchHandle is within T2_FFIRST_RSP_PARMS, covered by validate_t2()'s parameter-range check. No counterexample exists.
CIFSFindNext() — fs/smb/client/cifssmb.c FP confidence=medium
validate_t2() is called at line 4679 with sizeof(T2_FNEXT_RSP_PARMS) as the minimum parameter size argument before any tainted fields are extracted. This establishes that ParameterOffset + sizeof(T2_FNEXT_RSP_PARMS) fits within the received buffer, making all three struct field accesses (EndofSearch, SearchCount, LastNameOffset) safe. The lnoff value derived from LastNameOffset is separately bounds-checked against DataCount at line 4704. The validate_t2 guard precondition covers all three flagged dereferences since they are all within T2_FNEXT_RSP_PARMS.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 4686 |
| Taint snippet | response_data = (char *)&pSMBr->hdr.Protocol + |
| Tainted var | parms |
| Pointer deref | parms->EndofSearch line 4700 |
| Sink snippet | psrch_inf->endOfSearch = !!parms->EndofSearch; |
| Possibly guarded | no |
Dismissed: validate_t2() called at line 4679 with sizeof(T2_FNEXT_RSP_PARMS) validates that ParameterOffset region contains a complete T2_FNEXT_RSP_PARMS struct before parms is derived. EndofSearch is within that struct, so access is safe. Could not construct a counterexample — the validate_t2 check covers this field.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 4686 |
| Taint snippet | response_data = (char *)&pSMBr->hdr.Protocol + |
| Tainted var | parms |
| Pointer deref | parms->SearchCount line 4701 |
| Sink snippet | psrch_inf->entries_in_buffer = le16_to_cpu(parms->SearchCount); |
| Possibly guarded | no |
Dismissed: Same rationale as finding #1. SearchCount is a field within T2_FNEXT_RSP_PARMS, covered by the validate_t2() precondition. Could not construct a counterexample.
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 4686 |
| Taint snippet | response_data = (char *)&pSMBr->hdr.Protocol + |
| Tainted var | parms |
| Pointer deref | parms->LastNameOffset line 4703 |
| Sink snippet | lnoff = le16_to_cpu(parms->LastNameOffset); |
| Possibly guarded | no |
Dismissed: Same rationale. LastNameOffset is a field within T2_FNEXT_RSP_PARMS, covered by validate_t2(). The extracted lnoff value is further checked against DataCount at line 4704 before use as a pointer offset. Could not construct a counterexample.
CIFSGetExtAttr() — fs/smb/client/cifssmb.c FP confidence=high
validate_t2() is called before data_offset is extracted, with sizeof(struct file_chattr_info) as the minimum data size requirement. This validator checks that DataOffset + the minimum data size fits within the received packet buffer. Additionally, the count==16 check (matching sizeof(struct file_chattr_info)) confirms the data is exactly the right size. Together these guards make the pfinfo dereferences safe. No counterexample can be constructed that passes both validators yet causes OOB access.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 3765 |
| Taint snippet | __u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | pfinfo |
| Pointer deref | pfinfo->mode line 3777 |
| Sink snippet | *pExtAttrBits = le64_to_cpu(pfinfo->mode); |
| Possibly guarded | no |
Dismissed: validate_t2() called before taint source establishes DataOffset+sizeof(struct file_chattr_info) fits within packet. The count==16 check (sizeof(struct file_chattr_info)==16) further confirms bounds. Cannot construct counterexample: any data_offset that passes validate_t2() with the given minimum size is safe for pfinfo->mode access.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 3765 |
| Taint snippet | __u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | pfinfo |
| Pointer deref | pfinfo->mask line 3778 |
| Sink snippet | *pMask = le64_to_cpu(pfinfo->mask); |
| Possibly guarded | no |
Dismissed: Same analysis as finding #1 — pfinfo->mask is within the same struct file_chattr_info whose bounds are validated by validate_t2() before offset extraction, and confirmed by count==16. Cannot construct counterexample.
CIFSGetSrvInodeNumber() — fs/smb/client/cifssmb.c FP confidence=medium
validate_t2() is called at line 4823 before the tainted DataOffset is extracted at line 4831. validate_t2's postcondition establishes that DataOffset+DataCount fits within the received packet. The additional count<8 check at line 4835 ensures DataCount covers the sizeof(struct file_internal_info)=8 bytes. Together these guards make the pfinfo dereference safe against server-supplied values.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 4831 |
| Taint snippet | __u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | pfinfo |
| Pointer deref | pfinfo->UniqueId line 4843 |
| Sink snippet | *inode_number = le64_to_cpu(pfinfo->UniqueId); |
| Possibly guarded | no |
Dismissed: validate_t2() is called [called before taint source] with sizeof(struct file_internal_info) as minimum size, validating that DataOffset+DataCount fits within the packet. The count<8 guard further confirms DataCount>=sizeof(*pfinfo). No counterexample could be constructed: any DataOffset that passes validate_t2 and any DataCount>=8 guarantees pfinfo->UniqueId is in-bounds. False positive.
CIFSPOSIXCreate() — fs/smb/client/cifssmb.c FP confidence=medium
validate_t2() is called at line 1170 BEFORE psx_rsp is formed at line 1180, with the full required size (sizeof(OPEN_PSX_RSP) + sizeof(FILE_UNIX_BASIC_INFO)) as the minimum. In standard CIFS, validate_t2 checks that DataOffset plus the data fits within the received packet buffer, establishing a postcondition that psx_rsp (formed from that DataOffset) is safe to dereference for at least sizeof(OPEN_PSX_RSP) + sizeof(FILE_UNIX_BASIC_INFO) bytes. Additionally, the BCC check at line 1173 provides a secondary guard. The memcpy at line 1201 is further protected by the BCC check at lines 1195-1196. All findings are likely false positives due to validate_t2's precondition covering the tainted DataOffset before it is read.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 1180 |
| Taint snippet | psx_rsp = (OPEN_PSX_RSP *) |
| Tainted var | psx_rsp |
| Pointer deref | psx_rsp->OplockFlags line 1183 |
| Sink snippet | *pOplock = le16_to_cpu(psx_rsp->OplockFlags); |
| Possibly guarded | no |
Dismissed: validate_t2() called before line 1180 with sizeof(OPEN_PSX_RSP)+sizeof(FILE_UNIX_BASIC_INFO) validates that DataOffset + this size fits in the packet buffer. psx_rsp->OplockFlags is within sizeof(OPEN_PSX_RSP), so it is covered. Cannot construct a counterexample: any DataOffset that passes validate_t2 places psx_rsp safely within bounds.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 1180 |
| Taint snippet | psx_rsp = (OPEN_PSX_RSP *) |
| Tainted var | psx_rsp |
| Pointer deref | psx_rsp->Fid line 1185 |
| Sink snippet | *netfid = psx_rsp->Fid; /* cifs fid stays in le */ |
| Possibly guarded | no |
Dismissed: Same reasoning as finding #1. psx_rsp->Fid is within sizeof(OPEN_PSX_RSP), covered by validate_t2's postcondition. No counterexample possible.
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 1180 |
| Taint snippet | psx_rsp = (OPEN_PSX_RSP *) |
| Tainted var | psx_rsp |
| Pointer deref | psx_rsp->CreateAction line 1188 |
| Sink snippet | if (cpu_to_le32(FILE_CREATE) == psx_rsp->CreateAction) |
| Possibly guarded | no |
Dismissed: Same reasoning. psx_rsp->CreateAction is within sizeof(OPEN_PSX_RSP), covered by validate_t2. No counterexample possible.
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 1180 |
| Taint snippet | psx_rsp = (OPEN_PSX_RSP *) |
| Tainted var | psx_rsp |
| Pointer deref | psx_rsp->ReturnedLevel line 1191 |
| Sink snippet | if (psx_rsp->ReturnedLevel != cpu_to_le16(SMB_QUERY_FILE_UNIX_BASIC)) { |
| Possibly guarded | no |
Dismissed: Same reasoning. psx_rsp->ReturnedLevel is within sizeof(OPEN_PSX_RSP), covered by validate_t2. No counterexample possible.
Finding #5 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | le16_to_cpu() line 1180 |
| Taint snippet | psx_rsp = (OPEN_PSX_RSP *) |
| Tainted var | psx_rsp |
| Sink | memcpy() line 1201 (arg 1, role=pointer) |
| Sink snippet | memcpy(pRetData, |
| Possibly guarded | yes (heuristic) |
Dismissed: The memcpy at line 1201 reads sizeof(FILE_UNIX_BASIC_INFO) bytes from psx_rsp+sizeof(OPEN_PSX_RSP). validate_t2 was called with the total size covering both structs. Additionally, lines 1195-1196 explicitly check BCC >= sizeof(OPEN_PSX_RSP)+sizeof(FILE_UNIX_BASIC_INFO) before this branch. Both guards together make this safe. No counterexample possible.
CIFSSMBPosixLock() — fs/smb/client/cifssmb.c FP confidence=high
The function has a comprehensive validation sequence before dereferencing parm_data: (1) validate_t2() is called on the response, (2) BCC is checked against sizeof(*parm_data), (3) data_offset is checked against SMB_T2_MIN_OFFSET lower bound, (4) data_offset + sizeof(cifs_posix_lock) is checked against MAX_CIFS_SMALL_BUFFER_SIZE upper bound (the allocation size for small SMB buffers), and (5) data_count is checked against sizeof(cifs_posix_lock). The parm_data pointer is only formed after all these guards pass, and since pSMBr is a small buffer of exactly MAX_CIFS_SMALL_BUFFER_SIZE bytes, the upper-bound check guarantees the entire struct fits within the allocation. No counterexample can be constructed that passes all guards yet causes OOB access.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 2430 |
| Taint snippet | data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | parm_data |
| Pointer deref | parm_data->lock_type line 2445 |
| Sink snippet | if (parm_data->lock_type == cpu_to_le16(CIFS_UNLCK)) |
| Possibly guarded | no |
Dismissed: data_offset is validated at lines 2432-2434: lower bound (>= SMB_T2_MIN_OFFSET) and upper bound (data_offset + sizeof(cifs_posix_lock) <= MAX_CIFS_SMALL_BUFFER_SIZE). pSMBr was allocated via small_smb_init which provides exactly MAX_CIFS_SMALL_BUFFER_SIZE bytes. No counterexample possible — guards are tight.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 2430 |
| Taint snippet | data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | parm_data |
| Pointer deref | parm_data->lock_type line 2448 |
| Sink snippet | if (parm_data->lock_type == |
| Possibly guarded | yes (heuristic) |
Dismissed: Same parm_data pointer as finding #1, same validation chain covers it. The access at line 2448 is within the else-branch after line 2445's check, still reading from the same validated pointer.
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 2430 |
| Taint snippet | data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | parm_data |
| Pointer deref | parm_data->lock_type line 2451 |
| Sink snippet | else if (parm_data->lock_type == |
| Possibly guarded | yes (heuristic) |
Dismissed: Same parm_data pointer validated by the bounds checks at lines 2432-2434 and 2438. No counterexample possible.
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 2430 |
| Taint snippet | data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | parm_data |
| Pointer deref | parm_data->start line 2455 |
| Sink snippet | pLockData->fl_start = le64_to_cpu(parm_data->start); |
| Possibly guarded | yes (heuristic) |
Dismissed: parm_data->start is within sizeof(cifs_posix_lock) of the validated pointer base. The upper-bound check at line 2433 ensures data_offset + sizeof(cifs_posix_lock) <= MAX_CIFS_SMALL_BUFFER_SIZE, so all fields including 'start' are within bounds.
Finding #5 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 2430 |
| Taint snippet | data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | parm_data |
| Pointer deref | parm_data->length line 2457 |
| Sink snippet | (le64_to_cpu(parm_data->length) ? |
| Possibly guarded | yes (heuristic) |
Dismissed: parm_data->length is within sizeof(cifs_posix_lock) of the validated pointer base. Same reasoning as finding #4.
Finding #6 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 2430 |
| Taint snippet | data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | parm_data |
| Pointer deref | parm_data->length line 2458 |
| Sink snippet | le64_to_cpu(parm_data->length) - 1 : 0); |
| Possibly guarded | yes (heuristic) |
Dismissed: Second read of parm_data->length at line 2458, same pointer, same validation. False positive.
Finding #7 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 2430 |
| Taint snippet | data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | parm_data |
| Pointer deref | parm_data->pid line 2459 |
| Sink snippet | pLockData->c.flc_pid = -le32_to_cpu(parm_data->pid); |
| Possibly guarded | yes (heuristic) |
Dismissed: parm_data->pid is within sizeof(cifs_posix_lock) of the validated pointer base. The upper-bound check at line 2432-2434 guarantees the full struct fits in the buffer. No counterexample possible.
CIFSSMBQAllEAs() — fs/smb/client/cifssmb.c FP confidence=high
The function has a layered validation strategy: (1) validate_t2() called before taint extraction ensures data_offset+sizeof(fealist) fits in packet, making ea_response_data struct field accesses safe; (2) list_len is bounded against end_of_smb before the loop; (3) the loop uses countdown arithmetic with underflow checks after each subtraction to catch malformed entries; (4) destination buffer checks guard memcpy/memcmp operations. All findings are false positives due to these guards.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 6279 |
| Taint snippet | data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | ea_response_data |
| Pointer deref | ea_response_data->list_len line 6283 |
| Sink snippet | list_len = le32_to_cpu(ea_response_data->list_len); |
| Possibly guarded | no |
Dismissed: validate_t2() is called at line 6264 with offsetof(struct fealist, list) as minimum data size, establishing that data_offset + sizeof(fealist header including list_len) fits within the packet. The ea_response_data->list_len dereference is safe. No counterexample possible.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 6279 |
| Taint snippet | data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | ea_response_data |
| Pointer deref | ea_response_data->list line 6305 |
| Sink snippet | temp_fea = &ea_response_data->list; |
| Possibly guarded | yes (heuristic) |
Dismissed: validate_t2() with offsetof(struct fealist, list) covers both list_len and list fields. Additionally, list_len is checked > 8 (line 6285) and bounded against end_of_smb (line 6295) before ea_response_data->list is taken as temp_fea. No counterexample possible.
Finding #3 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le32_to_cpu() line 6283 |
| Taint snippet | list_len = le32_to_cpu(ea_response_data->list_len); |
| Tainted var | list_len |
| Loop | while_loop line 6307 |
| Sink snippet | while (list_len > 0) { |
| Possibly guarded | yes (heuristic) |
Dismissed: list_len is bounded against end_of_smb at line 6295 before the loop. Inside the loop, list_len is decremented and checked for underflow (< 0) at lines 6314 and 6323 after each field size subtraction, providing per-iteration bounds validation. No counterexample: any list_len that passes the end_of_smb check and causes per-iteration underflow is caught and returned as EIO.
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 6279 |
| Taint snippet | data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | temp_fea |
| Pointer deref | temp_fea->name_len line 6320 |
| Sink snippet | name_len = temp_fea->name_len; |
| Possibly guarded | no |
Dismissed: Before accessing temp_fea->name_len, list_len is decremented by 4 (the fixed header size of struct fea) and checked for underflow (line 6314). Since list_len was bounded against end_of_smb, temp_fea is guaranteed to be at least 4 bytes before packet end when the check passes. name_len is __u8 so max 255.
Finding #5 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 6279 |
| Taint snippet | data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | temp_fea |
| Pointer deref | temp_fea->value_len line 6321 |
| Sink snippet | value_len = le16_to_cpu(temp_fea->value_len); |
| Possibly guarded | no |
Dismissed: Same guard as finding #4 protects temp_fea->value_len. The list_len < 0 check at line 6314 ensures at least 4 bytes remain, covering both name_len (1 byte) and value_len (2 bytes) fields of struct fea.
Finding #6 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 6279 |
| Taint snippet | data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | name_len |
| Sink | memcmp() line 6331 (arg 2, role=size) |
| Sink snippet | memcmp(ea_name, temp_ptr, name_len) == 0) { |
| Possibly guarded | yes (heuristic) |
Dismissed: name_len is a __u8 from temp_fea->name_len (max 255). It is bounded against list_len at line 6322-6326. The memcmp at line 6331 is first guarded by ea_name_len == name_len, so the comparison is against a caller-supplied length. Source buffer (temp_ptr) is within validated packet bounds. No OOB risk.
Finding #7 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | le16_to_cpu() line 6279 |
| Taint snippet | data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | temp_ptr |
| Sink | memcmp() line 6331 (arg 1, role=pointer) |
| Sink snippet | memcmp(ea_name, temp_ptr, name_len) == 0) { |
| Possibly guarded | yes (heuristic) |
Dismissed: temp_ptr is advanced within the bounds established by list_len (which is bounded by end_of_smb). The per-iteration list_len countdown ensures temp_ptr never reaches end_of_smb before the loop terminates. No counterexample possible.
Finding #8 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 6321 |
| Taint snippet | value_len = le16_to_cpu(temp_fea->value_len); |
| Tainted var | value_len |
| Sink | memcpy() line 6340 (arg 2, role=size) |
| Sink snippet | memcpy(EAData, temp_ptr, value_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: value_len is bounded against list_len (lines 6322-6326 ensure name_len+1+value_len <= remaining list_len, which itself is bounded against packet end). The destination check at line 6336 (value_len > buf_size → ERANGE) protects EAData. Both source and destination are bounded.
Finding #9 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | le16_to_cpu() line 6279 |
| Taint snippet | data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | temp_ptr |
| Sink | memcpy() line 6340 (arg 1, role=pointer) |
| Sink snippet | memcpy(EAData, temp_ptr, value_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: temp_ptr at the memcpy at line 6340 points past name_len+1 bytes from temp_fea, which is within the validated region (list_len countdown ensures this). The buf_size check at 6336 also guards the write side.
Finding #10 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 6279 |
| Taint snippet | data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | name_len |
| Sink | memcpy() line 6349 (arg 2, role=size) |
| Sink snippet | memcpy(EAData, temp_ptr, name_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: name_len is bounded by list_len countdown. The rc < buf_size check at line 6346 ensures the total accumulated size (including this name_len) fits in EAData before copying. name_len is __u8 (max 255) and source pointer is within validated packet bounds.
Finding #11 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | le16_to_cpu() line 6279 |
| Taint snippet | data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | temp_ptr |
| Sink | memcpy() line 6349 (arg 1, role=pointer) |
| Sink snippet | memcpy(EAData, temp_ptr, name_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: temp_ptr at line 6349 is within the validated packet region (bounded by list_len countdown against end_of_smb). The write destination (EAData) is protected by the rc < buf_size check at line 6346.
CIFSSMBQFSAttributeInfo() — fs/smb/client/cifssmb.c FP confidence=medium
The function calls validate_t2() with sizeof(FILE_SYSTEM_ATTRIBUTE_INFO) before using the server-supplied DataOffset. This validator, which is called before the taint source is extracted (line 5201 precedes line 5208), establishes that DataOffset+sizeof(FILE_SYSTEM_ATTRIBUTE_INFO) fits within the received buffer. The code then checks rc before entering the else branch that uses data_offset. The memcpy size is fixed at sizeof(FILE_SYSTEM_ATTRIBUTE_INFO), matching the validated size. Confidence is medium because the validate_t2() body is not shown — if it does NOT validate DataOffset bounds, the finding would be real.
Finding #1 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | le16_to_cpu() line 5208 |
| Taint snippet | __u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | response_data |
| Sink | memcpy() line 5213 (arg 1, role=pointer) |
| Sink snippet | memcpy(&tcon->fsAttrInfo, response_data, |
| Possibly guarded | no |
Dismissed: validate_t2() is called at line 5201 with sizeof(FILE_SYSTEM_ATTRIBUTE_INFO) as the minimum data size argument, and its return value gates entry into the else branch at line 5207. This follows the standard CIFS validation pattern (false positive pattern 5a): a validator called with DataOffset and sizeof(struct) establishes that DataOffset+sizeof(struct) fits within the received buffer, making the subsequent pointer arithmetic and memcpy safe. Could not construct a counterexample assuming validate_t2() performs the expected offset+size-within-buffer check. The BCC check (< 13) provides secondary validation.
CIFSSMBQFSDeviceInfo() — fs/smb/client/cifssmb.c FP confidence=medium
The function calls validate_t2() at line 5275 before extracting DataOffset, passing sizeof(FILE_SYSTEM_DEVICE_INFO) as the minimum required data size. This is the standard CIFS validation pattern for TRANSACTION2 responses. If validate_t2() verifies that DataOffset + sizeof(FILE_SYSTEM_DEVICE_INFO) fits within the received buffer (which its signature and usage pattern strongly suggest), then the subsequent pointer arithmetic and memcpy are safe. The scanner missed the validate_t2() call chain protection.
Finding #1 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | le16_to_cpu() line 5283 |
| Taint snippet | __u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | response_data |
| Sink | memcpy() line 5288 (arg 1, role=pointer) |
| Sink snippet | memcpy(&tcon->fsDevInfo, response_data, |
| Possibly guarded | no |
Dismissed: validate_t2() is called at line 5275 with sizeof(FILE_SYSTEM_DEVICE_INFO) as the minimum data size parameter BEFORE DataOffset is extracted at line 5283. The standard CIFS validate_t2() implementation checks that DataOffset is sane and that DataOffset + the minimum size fits within the response buffer. Only if rc==0 does code reach line 5283. Could not construct a counterexample: any DataOffset that would cause OOB access should fail validate_t2(). The BCC check at line 5277 is an additional guard. This is a false positive from the scanner missing the validate_t2() call chain.
CIFSSMBQFSInfo() — fs/smb/client/cifssmb.c FP confidence=high
validate_t2() is called at line 5111 with sizeof(FILE_SYSTEM_SIZE_INFO) as the minimum data size argument, BEFORE data_offset is extracted at line 5117. validate_t2() validates that DataOffset + DataCount fits within the received buffer and DataCount >= sizeof(FILE_SYSTEM_SIZE_INFO), establishing that the pointer formed from data_offset is safe to dereference for all fields of FILE_SYSTEM_SIZE_INFO. All four findings are false positives because the validator's postcondition covers the flagged accesses.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 5117 |
| Taint snippet | __u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | response_data |
| Pointer deref | response_data->BytesPerSector line 5124 |
| Sink snippet | le32_to_cpu(response_data->BytesPerSector) * |
| Possibly guarded | no |
Dismissed: validate_t2() is called before line 5117 with sizeof(FILE_SYSTEM_SIZE_INFO), ensuring data_offset + sizeof(FILE_SYSTEM_SIZE_INFO) <= buffer_end. No counterexample can be constructed: a data_offset causing OOB would fail validate_t2() and the else branch would not be reached.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 5117 |
| Taint snippet | __u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | response_data |
| Pointer deref | response_data->SectorsPerAllocationUnit line 5125 |
| Sink snippet | le32_to_cpu(response_data-> |
| Possibly guarded | no |
Dismissed: Same protection as finding #1. validate_t2() validates offset and size before data_offset is read, covering SectorsPerAllocationUnit which lies within FILE_SYSTEM_SIZE_INFO.
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 5117 |
| Taint snippet | __u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | response_data |
| Pointer deref | response_data->TotalAllocationUnits line 5135 |
| Sink snippet | le64_to_cpu(response_data->TotalAllocationUnits); |
| Possibly guarded | no |
Dismissed: Same protection as finding #1. TotalAllocationUnits lies within FILE_SYSTEM_SIZE_INFO, which validate_t2() confirms fits within the buffer.
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 5117 |
| Taint snippet | __u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | response_data |
| Pointer deref | response_data->AvailableAllocationUnits line 5137 |
| Sink snippet | le64_to_cpu(response_data->AvailableAllocationUnits); |
| Possibly guarded | no |
Dismissed: Same protection as finding #1. AvailableAllocationUnits lies within FILE_SYSTEM_SIZE_INFO, which validate_t2() confirms fits within the buffer.
CIFSSMBQFSPosixInfo() — fs/smb/client/cifssmb.c FP confidence=high
validate_t2() is called at line 5498 with sizeof(FILE_SYSTEM_POSIX_INFO) BEFORE the tainted DataOffset is extracted at line 5504. This validator establishes that DataOffset + sizeof(FILE_SYSTEM_POSIX_INFO) fits within the received buffer. All nine findings reference field accesses through response_data, all of which are within sizeof(FILE_SYSTEM_POSIX_INFO) of the validated base. The pattern matches false-positive pattern (a): 'validate_t2 was called with this offset and sizeof(*response_data) before the offset was extracted [called before taint source]'. The additional BCC check provides defense-in-depth but validate_t2 is the primary gate.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 5504 |
| Taint snippet | __u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | response_data |
| Pointer deref | response_data->BlockSize line 5510 |
| Sink snippet | le32_to_cpu(response_data->BlockSize); |
| Possibly guarded | no |
Dismissed: validate_t2() called at line 5498 with sizeof(FILE_SYSTEM_POSIX_INFO) before DataOffset is read. BlockSize is within that struct, so the access is covered. No counterexample possible: any DataOffset causing OOB would fail validate_t2 before reaching this code.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 5504 |
| Taint snippet | __u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | response_data |
| Pointer deref | response_data->TotalBlocks line 5519 |
| Sink snippet | le64_to_cpu(response_data->TotalBlocks); |
| Possibly guarded | no |
Dismissed: validate_t2() covers offset + sizeof(FILE_SYSTEM_POSIX_INFO). TotalBlocks is within the struct. False positive.
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 5504 |
| Taint snippet | __u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | response_data |
| Pointer deref | response_data->BlocksAvail line 5521 |
| Sink snippet | le64_to_cpu(response_data->BlocksAvail); |
| Possibly guarded | no |
Dismissed: validate_t2() covers offset + sizeof(FILE_SYSTEM_POSIX_INFO). BlocksAvail is within the struct. False positive.
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 5504 |
| Taint snippet | __u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | response_data |
| Pointer deref | response_data->UserBlocksAvail line 5522 |
| Sink snippet | if (response_data->UserBlocksAvail == cpu_to_le64(-1)) { |
| Possibly guarded | no |
Dismissed: validate_t2() covers offset + sizeof(FILE_SYSTEM_POSIX_INFO). UserBlocksAvail is within the struct. False positive.
Finding #5 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 5504 |
| Taint snippet | __u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | response_data |
| Pointer deref | response_data->UserBlocksAvail line 5526 |
| Sink snippet | le64_to_cpu(response_data->UserBlocksAvail); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as #4, conditional branch. validate_t2() is the primary guard. False positive.
Finding #6 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 5504 |
| Taint snippet | __u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | response_data |
| Pointer deref | response_data->TotalFileNodes line 5528 |
| Sink snippet | if (response_data->TotalFileNodes != cpu_to_le64(-1)) |
| Possibly guarded | no |
Dismissed: validate_t2() covers offset + sizeof(FILE_SYSTEM_POSIX_INFO). TotalFileNodes is within the struct. False positive.
Finding #7 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 5504 |
| Taint snippet | __u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | response_data |
| Pointer deref | response_data->TotalFileNodes line 5530 |
| Sink snippet | le64_to_cpu(response_data->TotalFileNodes); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as #6, conditional branch. validate_t2() is the primary guard. False positive.
Finding #8 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 5504 |
| Taint snippet | __u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | response_data |
| Pointer deref | response_data->FreeFileNodes line 5531 |
| Sink snippet | if (response_data->FreeFileNodes != cpu_to_le64(-1)) |
| Possibly guarded | no |
Dismissed: validate_t2() covers offset + sizeof(FILE_SYSTEM_POSIX_INFO). FreeFileNodes is within the struct. False positive.
Finding #9 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 5504 |
| Taint snippet | __u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | response_data |
| Pointer deref | response_data->FreeFileNodes line 5533 |
| Sink snippet | le64_to_cpu(response_data->FreeFileNodes); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as #8, conditional branch. validate_t2() is the primary guard. False positive.
CIFSSMBQFSUnixInfo() — fs/smb/client/cifssmb.c FP confidence=high
validate_t2() is called before the tainted data_offset is used, with exactly sizeof(FILE_SYSTEM_UNIX_INFO) as the data_size argument. The standard CIFS validate_t2 implementation verifies that DataOffset + data_size fits within the received buffer. The return value is checked (rc check), so the memcpy branch is only reached when validation succeeded. This is a textbook example of pre-validated DataOffset use — the validator's postcondition covers both the pointer arithmetic and the memcpy size.
Finding #1 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | le16_to_cpu() line 5355 |
| Taint snippet | __u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | response_data |
| Sink | memcpy() line 5360 (arg 1, role=pointer) |
| Sink snippet | memcpy(&tcon->fsUnixInfo, response_data, |
| Possibly guarded | no |
Dismissed: validate_t2() is invoked at line 5349 with sizeof(FILE_SYSTEM_UNIX_INFO) as the expected data size — precisely the amount later passed to memcpy. The standard kernel validate_t2 implementation checks that pSMBr->t2.DataOffset + data_size does not exceed the buffer boundary established by bytes_returned. Its return value guards the entire else branch containing the data_offset extraction and memcpy. No counterexample can be constructed: any DataOffset large enough to cause OOB would make validate_t2 return non-zero, preventing entry into the else block. The scanner's 'possibly guarded: no' assessment is incorrect because the guard is the validate_t2 call at the top of the else-if chain, not an inline conditional on data_offset itself.
CIFSSMBRead() — fs/smb/client/cifssmb.c FP confidence=high
CIFSSMBRead performs comprehensive validation of server-supplied values before use. data_length is bounded against both CIFSMaxBufSize and the requested count (protecting destination buffer), while the explicit pointer arithmetic check at line 1781 ensures data_offset+data_length stays within the received response buffer (protecting source read). Both the size and pointer arguments to memcpy are properly validated.
Finding #1 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 1765 |
| Taint snippet | int data_length = le16_to_cpu(pSMBr->DataLengthHigh); |
| Tainted var | data_length |
| Sink | memcpy() line 1789 (arg 2, role=size) |
| Sink snippet | memcpy(*buf, pReadData, data_length); |
| Possibly guarded | yes (heuristic) |
Dismissed: data_length is validated at line 1771 against CIFSMaxBufSize (bounds source buffer size) and against count (bounds destination buffer size, since caller allocated *buf for count bytes). The additional pointer range check at line 1781 ensures data_offset+data_length fits within rsp_iov. No counterexample exists: data_length <= count and data_length <= CIFSMaxBufSize prevent both OOB read and OOB write.
Finding #2 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | le16_to_cpu() line 1779 |
| Taint snippet | u16 data_offset = le16_to_cpu(pSMBr->DataOffset); |
| Tainted var | pReadData |
| Sink | memcpy() line 1789 (arg 1, role=pointer) |
| Sink snippet | memcpy(*buf, pReadData, data_length); |
| Possibly guarded | no |
Dismissed: pReadData is derived from server-supplied data_offset, but the guard at line 1781 checks exactly the same arithmetic: (char*)&pSMBr->hdr.Protocol + data_offset + data_length <= rsp_iov.iov_base + rsp_iov.iov_len. pReadData is only formed in the else-branch after this check passes. data_offset is u16 so no negative wrap. No counterexample found.
CIFSSMBUnixQuerySymLink() — fs/smb/client/cifssmb.c MIXED confidence=medium
pSMBr is a server response buffer. Both DataOffset and DataCount are genuinely server-supplied. validate_t2() is called before use, but its postcondition needs scrutiny. Finding #1 (data_start pointer arithmetic) is the more serious issue — DataOffset is not validated against the actual response buffer size, so an adversarial server could provide a DataOffset that places data_start outside the received buffer. Finding #2 (count/DataCount passed as maxlen to cifs_strndup_from_utf16) is a real issue too: count is server-supplied and not range-checked against bytes_returned or any buffer boundary, meaning cifs_utf16_bytes/cifs_from_utf16 could read beyond the received data, and kmalloc could be called with a very large or zero size.
Finding #1 — Category B — cross-function via cifs_strndup_from_utf16() — BUG oob_read
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 3005 |
| Taint snippet | data_start = ((char *) &pSMBr->hdr.Protocol) + |
| Tainted var | data_start |
| Call site | line 3014 — passes data_start to cifs_strndup_from_utf16() |
| Call snippet | *symlinkinfo = cifs_strndup_from_utf16(data_start, |
| Sink (in callee) | kmalloc() line 341 (arg 0, role=size) |
| Sink snippet | dst = kmalloc(len, GFP_KERNEL); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: KASAN: slab-out-of-bounds or heap-use-after-bounds when reading from data_start in cifs_strndup_from_utf16; a malicious SMB server returning a large DataOffset could point data_start past the end of the allocated response buffer
Fix: After computing data_start, validate that DataOffset is within the actual received response: check that (le16_to_cpu(pSMBr->t2.DataOffset) + count) does not exceed bytes_returned (or the actual buffer size). Reject the response with an error if the offset places data_start outside the buffer.
CVE pattern: CVE-2022-NNNN style DataOffset OOB in SMB1/CIFS transaction2 response parsing
Finding #2 — Category B — cross-function via cifs_strndup_from_utf16() — BUG oob_read
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 3003 |
| Taint snippet | u16 count = le16_to_cpu(pSMBr->t2.DataCount); |
| Tainted var | count |
| Call site | line 3014 — passes count to cifs_strndup_from_utf16() |
| Call snippet | *symlinkinfo = cifs_strndup_from_utf16(data_start, |
| Sink (in callee) | kmalloc() line 341 (arg 0, role=size) |
| Sink snippet | dst = kmalloc(len, GFP_KERNEL); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: KASAN: slab-out-of-bounds in cifs_utf16_bytes or cifs_from_utf16 when count exceeds the actual data in the response buffer; or an oversized kmalloc allocation
Fix: Validate count against the actual number of data bytes available in the response (bytes_returned minus the DataOffset). Reject responses where count exceeds available data. Also ensure count > 0 before calling cifs_strndup_from_utf16.
CVE pattern: SMB1 CIFS transaction2 DataCount not validated against actual response buffer size
SMBOldQFSInfo() — fs/smb/client/cifssmb.c FP confidence=medium
validate_t2() is called before data_offset is extracted (line 5021), passing sizeof(FILE_SYSTEM_ALLOC_INFO) as the minimum data size. This validator reads DataOffset from the response and checks that DataOffset + the data count fits within the received packet bounds. Combined with the rc check on line 5023, the response_data pointer dereferences are protected. The data_offset value IS server-supplied (genuine taint), but the bounds check via validate_t2 is sufficient to prevent OOB access. No counterexample could be constructed where validate_t2 passes yet response_data points outside the packet.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 5027 |
| Taint snippet | __u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | response_data |
| Pointer deref | response_data->BytesPerSector line 5034 |
| Sink snippet | le16_to_cpu(response_data->BytesPerSector) * |
| Possibly guarded | no |
Dismissed: validate_t2() called at line 5021 with sizeof(FILE_SYSTEM_ALLOC_INFO) validates that DataOffset + data size fits within the packet buffer before data_offset is extracted on line 5027. The rc check gates entry to the dereference block. No counterexample found.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 5027 |
| Taint snippet | __u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | response_data |
| Pointer deref | response_data->SectorsPerAllocationUnit line 5035 |
| Sink snippet | le32_to_cpu(response_data-> |
| Possibly guarded | no |
Dismissed: Same protection as finding #1 — validate_t2() with sizeof(FILE_SYSTEM_ALLOC_INFO) covers this field access. No counterexample found.
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 5027 |
| Taint snippet | __u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | response_data |
| Pointer deref | response_data->TotalAllocationUnits line 5045 |
| Sink snippet | le32_to_cpu(response_data->TotalAllocationUnits); |
| Possibly guarded | no |
Dismissed: Same protection as finding #1 — validate_t2() with sizeof(FILE_SYSTEM_ALLOC_INFO) covers TotalAllocationUnits field. No counterexample found.
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 5027 |
| Taint snippet | __u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | response_data |
| Pointer deref | response_data->FreeAllocationUnits line 5047 |
| Sink snippet | le32_to_cpu(response_data->FreeAllocationUnits); |
| Possibly guarded | no |
Dismissed: Same protection as finding #1 — validate_t2() with sizeof(FILE_SYSTEM_ALLOC_INFO) covers FreeAllocationUnits field. No counterexample found.
cifs_create_reparse_inode() — fs/smb/client/cifssmb.c BUG confidence=medium
The xattr_iov buffer appears to be locally constructed rather than a server response, but the EA traversal loop lacks bounds checking on next_entry_offset relative to iov_len, and CIFSSMBSetEA() explicitly acknowledges (via TODO comment) that ea_value_len is not verified to fit within the negotiated SMB buffer size.
Finding #1 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 3222 |
| Taint snippet | ea = (struct smb2_file_full_ea_info *)((u8 *)ea + |
| Tainted var | ea |
| Pointer deref | ea->next_entry_offset line 3223 |
| Sink snippet | le32_to_cpu(ea->next_entry_offset)); |
| Possibly guarded | no |
| Server-supplied | no |
| Check present | yes |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in cifs_create_reparse_inode — reading beyond xattr_iov buffer if next_entry_offset points past the buffer end
Fix: Before advancing 'ea', verify that (u8*)ea + le32_to_cpu(ea->next_entry_offset) + sizeof(*ea) <= (u8*)xattr_iov->iov_base + xattr_iov->iov_len, and also check that next_entry_offset >= sizeof(*ea) to prevent infinite loops or backward movement.
CVE pattern: linked-list traversal without bounds checking on offset field
Finding #2 — Category B — cross-function via CIFSSMBSetEA() — BUG oob_write
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 3210 |
| Taint snippet | rc = CIFSSMBSetEA(xid, |
| Tainted var | le16_to_cpu(ea->ea_value_length) |
| Call site | line 3210 — passes le16_to_cpu(ea->ea_value_length) to CIFSSMBSetEA() |
| Call snippet | rc = CIFSSMBSetEA(xid, |
| Sink (in callee) | memcpy() line 6459 (arg 2, role=size) |
| Sink snippet | memcpy(parm_data->list.name + name_len + 1, |
| Possibly guarded | no |
| Server-supplied | no |
| Check present | no |
| Check sufficient | no |
Symptom: kernel heap corruption / KASAN slab-out-of-bounds in CIFSSMBSetEA memcpy if ea_value_length exceeds SMB buffer space
Fix: In CIFSSMBSetEA(), add the missing bounds check: verify that name_len + 1 + ea_value_len fits within the allocated SMB buffer (i.e., offset + sizeof(*parm_data) + name_len + 1 + ea_value_len <= buffer_size). The TODO comment in CIFSSMBSetEA() at line 6455 explicitly notes this check is missing.
CVE pattern: missing upper-bound validation before memcpy with caller-supplied length
cifs_do_get_acl() — fs/smb/client/cifssmb.c FP confidence=high
Both findings are false positives. validate_t2() is called before the taint sources are read, establishing that DataOffset and DataCount are within the received buffer. The cross-function analysis incorrectly attributes the subscript role: data_offset (finding #1) is used as a pointer offset (guarded by validate_t2), not as the array subscript at line 3412; the caller's count/size_of_data_area (finding #2) maps to the bound parameter in cifs_to_posix_acl(), not the subscript. Inside cifs_to_posix_acl(), the local 'count' derived from cifs_acl->access_entry_count is the actual subscript, and it is validated against size_of_data_area before use at line 3409, making OOB access impossible.
Finding #1 — Category C — cross-function via cifs_to_posix_acl() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 3591 |
| Taint snippet | __u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| Tainted var | data_offset |
| Call site | line 3593 — passes data_offset to cifs_to_posix_acl() |
| Call snippet | rc = cifs_to_posix_acl(acl, |
| Subscript (in callee) | [] line 3412 |
| Sink snippet | pACE = &cifs_acl->ace_array[count]; |
| Possibly guarded | no |
Dismissed: data_offset is used to form the src pointer passed to cifs_to_posix_acl(), not as an array subscript. validate_t2() called before line 3591 establishes that data_off+DataCount fits within the received buffer, so the pointer arithmetic is safe. The scanner misidentified the sink: the subscript at line 3412 is the callee-local 'count' (from cifs_acl->access_entry_count), which is a different variable entirely. No counterexample can be constructed.
Finding #2 — Category C — cross-function via cifs_to_posix_acl() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 3592 |
| Taint snippet | __u16 count = le16_to_cpu(pSMBr->t2.DataCount); |
| Tainted var | count |
| Call site | line 3593 — passes count to cifs_to_posix_acl() |
| Call snippet | rc = cifs_to_posix_acl(acl, |
| Subscript (in callee) | [] line 3412 |
| Sink snippet | pACE = &cifs_acl->ace_array[count]; |
| Possibly guarded | no |
Dismissed: The caller's 'count' (DataCount) maps to 'size_of_data_area' in cifs_to_posix_acl() — it is used as a bound, not a subscript. The actual subscript at line 3412 is the callee-local 'count' from cifs_acl->access_entry_count. This is validated at lines 3406-3410 before line 3412 is reached: if sizeof(cifs_posix_acl)+sizeof(cifs_posix_ace)*count > size_of_data_area, the function returns -EINVAL. validate_t2() additionally ensures size_of_data_area is within the buffer. No counterexample can be constructed — any access_entry_count large enough to cause OOB is caught by the guard.
cifs_query_reparse_point() — fs/smb/client/cifssmb.c FP confidence=high
The function performs explicit pointer-arithmetic bounds checking at lines 3127-3132: it computes 'end' from the BCC byte count, computes 'start' from data_offset, checks start >= end, and checks (end - start) < sizeof(*buf). This guarantees that at least sizeof(struct reparse_data_buffer) bytes are accessible at 'buf' before any field access. All three findings concern accesses to fields within sizeof(*buf), which are fully covered by this validation. No counterexample can be constructed that would pass the guard and still cause OOB access.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 3098 |
| Taint snippet | data_offset = le32_to_cpu(io_rsp->DataOffset); |
| Tainted var | buf |
| Pointer deref | buf->ReparseDataLength line 3138 |
| Sink snippet | data_count < le16_to_cpu(buf->ReparseDataLength) + len) { |
| Possibly guarded | yes (heuristic) |
Dismissed: The check at line 3127 '(size_t)(end - start) < sizeof(*buf)' guarantees at least sizeof(reparse_data_buffer) bytes are available at 'start'/'buf'. ReparseDataLength is a field within that struct, so the access is safe. No counterexample: any data_offset that would put ReparseDataLength out of bounds fails the sizeof(*buf) check.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 3098 |
| Taint snippet | data_offset = le32_to_cpu(io_rsp->DataOffset); |
| Tainted var | buf |
| Pointer deref | buf->ReparseDataLength line 3140 |
| Sink snippet | data_count, le16_to_cpu(buf->ReparseDataLength) + len); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same access as finding #1 — this is the error-reporting path inside the same conditional. The sizeof(*buf) guard at line 3127 covers this dereference. False positive.
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 3098 |
| Taint snippet | data_offset = le32_to_cpu(io_rsp->DataOffset); |
| Tainted var | buf |
| Pointer deref | buf->ReparseTag line 3144 |
| Sink snippet | *tag = le32_to_cpu(buf->ReparseTag); |
| Possibly guarded | yes (heuristic) |
Dismissed: buf->ReparseTag is within sizeof(reparse_data_buffer) which is validated at line 3127. Additionally, the checks at lines 3137-3142 must pass (goto error on failure) before reaching line 3144, providing a further validation layer. False positive.
cifs_to_posix_acl() — fs/smb/client/cifssmb.c FP confidence=high
The function has proper bounds validation before using server-supplied count values as array subscripts. In the ACL_TYPE_DEFAULT branch, access_entry_count is validated via a size check (lines 3409-3410) before being used as an array subscript at line 3412. The check confirms that sizeof(cifs_posix_acl) + sizeof(cifs_posix_ace)*count <= size_of_data_area, ensuring the pointer arithmetic stays within buffer bounds. The count field is a __u16 (max 65535) and sizeof(cifs_posix_ace) is small enough to avoid integer overflow in the size computation.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 3405 |
| Taint snippet | count = le16_to_cpu(cifs_acl->access_entry_count); |
| Tainted var | count |
| Subscript | [] line 3412 |
| Sink snippet | pACE = &cifs_acl->ace_array[count]; |
| Possibly guarded | no |
Dismissed: The bounds check at lines 3409-3410 validates that sizeof(struct cifs_posix_acl) + sizeof(struct cifs_posix_ace) * count <= size_of_data_area before the subscript access at line 3412. This guarantees ace_array[count] is within the buffer. No counterexample exists: any count large enough to cause OOB would fail the size check and return -EINVAL first. The __u16 type of count (max 65535) combined with small sizeof(cifs_posix_ace) means no integer overflow in the size multiplication.
parse_dfs_referrals() — fs/smb/client/misc.c FP confidence=high
parse_dfs_referrals() has solid validation discipline: rsp_size checked against sizeof(*rsp) and num_referrals*sizeof(REFERRAL3); PathConsumed is bounded against searchName length before use; DfsPathOffset and NetworkAddressOffset are each validated to be >= sizeof(*ref) and <= data_end-(char*)ref before pointer arithmetic. The scanner's cross-function taint tracking overfired in all three cases.
Finding #1 — Category F — cross-function via cifs_utf16_bytes() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le16_to_cpu() line 757 |
| Taint snippet | path_consumed = le16_to_cpu(rsp->PathConsumed); |
| Tainted var | path_consumed |
| Call site | line 774 — passes path_consumed to cifs_utf16_bytes() |
| Call snippet | node->path_consumed = cifs_utf16_bytes(tmp, path_consumed, |
| Loop | for_loop line 299 |
| Sink snippet | for (i = 0; i < maxwords; i++) { |
| Possibly guarded | yes (heuristic) |
Dismissed: Guard at line 762 ensures path_consumed <= search_name_utf16_len (= search_name_len*2+2), and tmp is allocated with that exact size. cifs_utf16_bytes iterates maxwords = path_consumed/2 <= search_name_utf16_len/2 words over tmp, staying within bounds. No counterexample possible — the guard is tight.
Finding #2 — Category B — cross-function via cifs_strndup_from_utf16() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 795 |
| Taint snippet | temp = (char *)ref + le16_to_cpu(ref->DfsPathOffset); |
| Tainted var | temp |
| Call site | line 797 — passes temp to cifs_strndup_from_utf16() |
| Call snippet | node->path_name = cifs_strndup_from_utf16(temp, max_len, |
| Sink (in callee) | kmalloc() line 341 (arg 0, role=size) |
| Sink snippet | dst = kmalloc(len, GFP_KERNEL); |
| Possibly guarded | no |
Dismissed: temp is the src pointer (arg 0), not the size argument. The scanner incorrectly traces temp as a size to kmalloc. The actual allocation size in cifs_strndup_from_utf16 is computed from cifs_utf16_bytes(src, max_len, ...) which is bounded by max_len. DfsPathOffset is validated at lines 790-794 ensuring temp is within [ref+sizeof(*ref), data_end]. False positive due to taint tracking through pointer arithmetic into kmalloc size.
Finding #3 — Category B — cross-function via cifs_strndup_from_utf16() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 795 |
| Taint snippet | temp = (char *)ref + le16_to_cpu(ref->DfsPathOffset); |
| Tainted var | max_len |
| Call site | line 797 — passes max_len to cifs_strndup_from_utf16() |
| Call snippet | node->path_name = cifs_strndup_from_utf16(temp, max_len, |
| Sink (in callee) | kmalloc() line 341 (arg 0, role=size) |
| Sink snippet | dst = kmalloc(len, GFP_KERNEL); |
| Possibly guarded | no |
Dismissed: max_len = data_end - temp, where temp is validated to lie within [ref+sizeof(*ref), data_end], so max_len is in [0, rsp_size]. In cifs_strndup_from_utf16, len = cifs_utf16_bytes(src, max_len, ...) + nls_nullsize. cifs_utf16_bytes scans at most max_len/2 UTF-16 words and returns output byte count — bounded by input. No unbounded allocation possible. False positive.
cnvrtDosUnixTm() — fs/smb/client/netmisc.c FP confidence=high
The function extracts server-supplied DOS date/time fields and validates them before use. Month is clamped to [1,12] by line 184 before the array subscript at line 187; after the 'month -= 1' adjustment, the subscript is in [0,11], which is within bounds for a 12-element total_days_of_prev_months array. The static scanner incorrectly reported 'possibly guarded: no' — the clamp is unconditionally applied inside the validation block triggered by the out-of-range check.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 164 |
| Taint snippet | u16 date = le16_to_cpu(le_date); |
| Tainted var | month |
| Subscript | [] line 187 |
| Sink snippet | days = day + total_days_of_prev_months[month]; |
| Possibly guarded | no |
Dismissed: Month is validated at line 181 and clamped to [1,12] at line 184 before the array access. After 'month -= 1' at line 186, the subscript is in [0,11]. No counterexample can be constructed: any out-of-range server value triggers the clamp, bringing month into a safe range. The scanner missed the clamp within the conditional block, producing a false positive.
mknod_wsl() — fs/smb/client/reparse.c FP confidence=high
Both findings are false positives. `cc` comes from `wsl_set_xattrs()`, a local request-builder that constructs the xattr buffer itself — `cc->ctx.DataLength` is a kernel-written value read back via le32_to_cpu, not a server-supplied value. Similarly, `buf` from `wsl_set_reparse_buf()` is locally constructed. Finding #2 is additionally a false positive because `cifs_free_open_info()` uses `sizeof(*data)` as the memset size — a compile-time constant entirely independent of any tainted value.
Finding #1 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le32_to_cpu() line 703 |
| Taint snippet | len = le32_to_cpu(cc->ctx.DataLength); |
| Tainted var | len |
| Sink | memcpy() line 704 (arg 2, role=size) |
| Sink snippet | memcpy(data.wsl.eas, &cc->ea, len); |
| Possibly guarded | no |
Dismissed: cc->ctx.DataLength is set by wsl_set_xattrs(), a local kernel function that constructs the xattr request buffer. The le32_to_cpu() is a round-trip read of a kernel-written cpu_to_le32() value, not a server-supplied value. Pattern #1 false positive: locally-built struct field misidentified as server-supplied.
Finding #2 — Category B — cross-function via cifs_free_open_info() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le32_to_cpu() line 696 |
| Taint snippet | data = (struct cifs_open_info_data) { |
| Tainted var | data |
| Call site | line 715 — passes data to cifs_free_open_info() |
| Call snippet | cifs_free_open_info(&data); |
| Sink (in callee) | memset() line 490 (arg 2, role=size) |
| Sink snippet | memset(data, 0, sizeof(*data)); |
| Possibly guarded | no |
Dismissed: The memset size in cifs_free_open_info() is sizeof(*data) — a compile-time constant. The scanner incorrectly attributed taint from the struct initializer to the memset size argument. Additionally, buf->ReparseTag comes from wsl_set_reparse_buf(), a local request-builder, so the le32_to_cpu() read is not server-supplied. Double false positive: wrong taint source AND wrong sink attribution.
parse_reparse_native_symlink() — fs/smb/client/reparse.c FP confidence=high
The bounds check at line 1020 ('offs + 20 > plen || offs + len + 20 > plen') properly validates both offs and len against the packet buffer size before use. The call to smb2_parse_native_symlink() passes a pointer (sym->PathBuffer + offs) and length (len) that are already validated to be within plen. Inside the callee, allocation sizes are derived from strlen() calls on strings parsed from the length-bounded input buffer — not directly from raw offs or len values. The static analyzer over-taints by tracking offs through pointer arithmetic into string content and then into strlen()-derived sizes, but these paths are properly bounded. No counterexample exists where passing the line-1020 guard still allows OOB access in the callee sinks.
Finding #1 — Category B — cross-function via smb2_parse_native_symlink() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 1018 |
| Taint snippet | offs = le16_to_cpu(sym->SubstituteNameOffset); |
| Tainted var | offs |
| Call site | line 1026 — passes offs to smb2_parse_native_symlink() |
| Call snippet | return smb2_parse_native_symlink(&data->symlink_target, |
| Sink (in callee) | kmalloc() line 944 (arg 0, role=size_mul_overflow) |
| Sink snippet | linux_target = kmalloc(symlinkroot_len + 1 + abs_path_len, GFP_KERNEL); |
| Possibly guarded | yes (heuristic) |
Dismissed: offs is checked: offs+20 <= plen before use. The kmalloc at line 944 uses abs_path_len=strlen(abs_path)+1, derived from string content within the len-bounded buffer, not from raw offs. No counterexample found.
Finding #2 — Category A — cross-function via smb2_parse_native_symlink() — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | le16_to_cpu() line 1018 |
| Taint snippet | offs = le16_to_cpu(sym->SubstituteNameOffset); |
| Tainted var | offs |
| Call site | line 1026 — passes offs to smb2_parse_native_symlink() |
| Call snippet | return smb2_parse_native_symlink(&data->symlink_target, |
| Sink (in callee) | memcpy() line 949 (arg 0, role=pointer) |
| Sink snippet | memcpy(linux_target, symroot, symlinkroot_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: The memcpy at line 949 copies from symroot (kernel-local string) to linux_target (freshly allocated). The destination pointer is not derived from offs. The taint flow through offs to this sink is spurious; offs only determines the source pointer into a bounds-checked region.
Finding #3 — Category B — cross-function via smb2_parse_native_symlink() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 1018 |
| Taint snippet | offs = le16_to_cpu(sym->SubstituteNameOffset); |
| Tainted var | offs |
| Call site | line 1026 — passes offs to smb2_parse_native_symlink() |
| Call snippet | return smb2_parse_native_symlink(&data->symlink_target, |
| Sink (in callee) | memcpy() line 951 (arg 2, role=size) |
| Sink snippet | memcpy(linux_target + symlinkroot_len + 1, abs_path, abs_path_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: memcpy size is abs_path_len=strlen(abs_path)+1, bounded by string content from len-validated buffer. No counterexample found.
Finding #4 — Category B — cross-function via smb2_parse_native_symlink() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 1018 |
| Taint snippet | offs = le16_to_cpu(sym->SubstituteNameOffset); |
| Tainted var | offs |
| Call site | line 1026 — passes offs to smb2_parse_native_symlink() |
| Call snippet | return smb2_parse_native_symlink(&data->symlink_target, |
| Sink (in callee) | kmalloc() line 969 (arg 0, role=size_mul_overflow) |
| Sink snippet | linux_target = kmalloc(levels*3 + smb_target_len, GFP_KERNEL); |
| Possibly guarded | yes (heuristic) |
Dismissed: kmalloc at line 969 uses levels*3+smb_target_len. levels is counted from full_path (kernel-controlled). smb_target_len=strlen(smb_target)+1 comes from parsed buffer content bounded by len. No counterexample found.
Finding #5 — Category B — cross-function via smb2_parse_native_symlink() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 1018 |
| Taint snippet | offs = le16_to_cpu(sym->SubstituteNameOffset); |
| Tainted var | offs |
| Call site | line 1026 — passes offs to smb2_parse_native_symlink() |
| Call snippet | return smb2_parse_native_symlink(&data->symlink_target, |
| Sink (in callee) | memcpy() line 979 (arg 2, role=size) |
| Sink snippet | memcpy(linux_target + levels*3, smb_target+1, smb_target_len); /* +1 to skip leading sep */ |
| Possibly guarded | yes (heuristic) |
Dismissed: memcpy size is smb_target_len=strlen(smb_target)+1, bounded by len-validated content. No counterexample found.
Finding #6 — Category B — cross-function via smb2_parse_native_symlink() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 1019 |
| Taint snippet | len = le16_to_cpu(sym->SubstituteNameLength); |
| Tainted var | len |
| Call site | line 1026 — passes len to smb2_parse_native_symlink() |
| Call snippet | return smb2_parse_native_symlink(&data->symlink_target, |
| Sink (in callee) | kmalloc() line 944 (arg 0, role=size_mul_overflow) |
| Sink snippet | linux_target = kmalloc(symlinkroot_len + 1 + abs_path_len, GFP_KERNEL); |
| Possibly guarded | yes (heuristic) |
Dismissed: len is checked: offs+len+20 <= plen. kmalloc at line 944 uses strlen-derived size from the len-bounded buffer. No counterexample found.
Finding #7 — Category A — cross-function via smb2_parse_native_symlink() — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | le16_to_cpu() line 1019 |
| Taint snippet | len = le16_to_cpu(sym->SubstituteNameLength); |
| Tainted var | len |
| Call site | line 1026 — passes len to smb2_parse_native_symlink() |
| Call snippet | return smb2_parse_native_symlink(&data->symlink_target, |
| Sink (in callee) | memcpy() line 949 (arg 0, role=pointer) |
| Sink snippet | memcpy(linux_target, symroot, symlinkroot_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: memcpy destination (linux_target) is a freshly allocated buffer. Taint flow from len to this pointer sink is spurious. No counterexample found.
Finding #8 — Category B — cross-function via smb2_parse_native_symlink() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 1019 |
| Taint snippet | len = le16_to_cpu(sym->SubstituteNameLength); |
| Tainted var | len |
| Call site | line 1026 — passes len to smb2_parse_native_symlink() |
| Call snippet | return smb2_parse_native_symlink(&data->symlink_target, |
| Sink (in callee) | memcpy() line 951 (arg 2, role=size) |
| Sink snippet | memcpy(linux_target + symlinkroot_len + 1, abs_path, abs_path_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: memcpy size is abs_path_len derived from strlen within bounds set by validated len. No counterexample found.
Finding #9 — Category B — cross-function via smb2_parse_native_symlink() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 1019 |
| Taint snippet | len = le16_to_cpu(sym->SubstituteNameLength); |
| Tainted var | len |
| Call site | line 1026 — passes len to smb2_parse_native_symlink() |
| Call snippet | return smb2_parse_native_symlink(&data->symlink_target, |
| Sink (in callee) | kmalloc() line 969 (arg 0, role=size_mul_overflow) |
| Sink snippet | linux_target = kmalloc(levels*3 + smb_target_len, GFP_KERNEL); |
| Possibly guarded | yes (heuristic) |
Dismissed: kmalloc size uses levels (from kernel full_path) and smb_target_len (strlen of parsed, len-bounded string). No counterexample found.
Finding #10 — Category B — cross-function via smb2_parse_native_symlink() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 1019 |
| Taint snippet | len = le16_to_cpu(sym->SubstituteNameLength); |
| Tainted var | len |
| Call site | line 1026 — passes len to smb2_parse_native_symlink() |
| Call snippet | return smb2_parse_native_symlink(&data->symlink_target, |
| Sink (in callee) | memcpy() line 979 (arg 2, role=size) |
| Sink snippet | memcpy(linux_target + levels*3, smb_target+1, smb_target_len); /* +1 to skip leading sep */ |
| Possibly guarded | yes (heuristic) |
Dismissed: memcpy size smb_target_len derives from strlen on len-bounded buffer content. No counterexample found.
parse_reparse_nfs() — fs/smb/client/reparse.c FP confidence=high
parse_reparse_nfs() applies a thorough validation chain before using server-supplied len: (1) it checks that offsetof(InodeType)+len fits within plen, bounding the entire data region; (2) it checks len >= sizeof(InodeType); (3) it subtracts sizeof(InodeType) to get DataBuffer len; (4) for symlinks it checks len>0 and len%2==0; (5) UniStrnlen confirms no embedded nulls. The resulting len passed to cifs_strndup_from_utf16() is tightly bounded by the received buffer size (plen), which itself is a kernel-controlled value. Inside cifs_strndup_from_utf16(), kmalloc receives the output of cifs_utf16_bytes() which scans up to maxlen bytes, so the allocation is safe.
Finding #1 — Category B — cross-function via cifs_strndup_from_utf16() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 749 |
| Taint snippet | len = le16_to_cpu(buf->ReparseDataLength); |
| Tainted var | len |
| Call site | line 776 — passes len to cifs_strndup_from_utf16() |
| Call snippet | data->symlink_target = cifs_strndup_from_utf16(buf->DataBuffer, |
| Sink (in callee) | kmalloc() line 341 (arg 0, role=size) |
| Sink snippet | dst = kmalloc(len, GFP_KERNEL); |
| Possibly guarded | yes (heuristic) |
Dismissed: No counterexample can be constructed: len is a u16 value (max 65535), already validated to fit within plen at line 750 and reduced by sizeof(InodeType) at line 760. The kmalloc inside cifs_strndup_from_utf16() sizes based on cifs_utf16_bytes() output (at most maxlen=len bytes) plus nls_nullsize, both trivially bounded. No integer overflow is possible. The scanner flagged the taint flow but the guards are tight enough to prevent any unsafe memory operation.
parse_reparse_wsl_symlink() — fs/smb/client/reparse.c FP confidence=high
The function has proper validation: the server-supplied ReparseDataLength (u16, max 65535) is checked against plen and data_offset before use. symname_utf8_len = len - data_offset is bounded to at most ~65535, so symname_utf8_len * 2 cannot overflow a 32-bit int or size_t. The cross-function findings are also false positives: finding #2 flags the buffer pointer (not a size), and finding #3's symname_utf16_len is bounded by utf8s_to_utf16s() output which is capped by symname_utf8_len.
Finding #1 — Category B — INTEGER OVERFLOW — false positive
| Category | Cat B — integer overflow: symname_utf8_len * 2 |
|---|---|
| Taint source | le16_to_cpu() line 1039 |
| Taint snippet | int len = le16_to_cpu(buf->ReparseDataLength); |
| Tainted var | symname_utf8_len |
| Overflow expr | symname_utf8_len * 2 |
| Safe fix | kmalloc_array() or check_mul_overflow() |
| Sink | kzalloc() line 1076 (arg 0, role=size_mul_overflow) |
| Sink snippet | symname_utf16 = kzalloc(symname_utf8_len * 2, GFP_KERNEL); |
| Possibly guarded | yes (heuristic) |
Dismissed: len comes from le16_to_cpu() so max value is 65535. symname_utf8_len = len - data_offset is at most ~65535. symname_utf8_len * 2 <= ~131070, which cannot overflow int32 or size_t. No counterexample exists that would cause overflow. False positive.
Finding #2 — Category B — cross-function via cifs_strndup_from_utf16() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 1039 |
| Taint snippet | int len = le16_to_cpu(buf->ReparseDataLength); |
| Tainted var | symname_utf16 |
| Call site | line 1088 — passes symname_utf16 to cifs_strndup_from_utf16() |
| Call snippet | data->symlink_target = cifs_strndup_from_utf16((u8 *)symname_utf16, |
| Sink (in callee) | kmalloc() line 341 (arg 0, role=size) |
| Sink snippet | dst = kmalloc(len, GFP_KERNEL); |
| Possibly guarded | no |
Dismissed: symname_utf16 is the buffer pointer (not a size) passed as src to cifs_strndup_from_utf16(). The scanner incorrectly identifies the pointer as feeding a size sink. The actual kmalloc size in the callee is derived from cifs_utf16_bytes() applied to the buffer contents with maxlen bound, not from the pointer value itself. False positive.
Finding #3 — Category B — cross-function via cifs_strndup_from_utf16() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 1039 |
| Taint snippet | int len = le16_to_cpu(buf->ReparseDataLength); |
| Tainted var | symname_utf16_len |
| Call site | line 1088 — passes symname_utf16_len to cifs_strndup_from_utf16() |
| Call snippet | data->symlink_target = cifs_strndup_from_utf16((u8 *)symname_utf16, |
| Sink (in callee) | kmalloc() line 341 (arg 0, role=size) |
| Sink snippet | dst = kmalloc(len, GFP_KERNEL); |
| Possibly guarded | yes (heuristic) |
Dismissed: symname_utf16_len = utf8s_to_utf16s(..., symname_utf8_len) * 2. utf8s_to_utf16s() is bounded by the outlen argument (symname_utf8_len), so symname_utf16_len <= symname_utf8_len * 2 <= ~131070. In cifs_strndup_from_utf16(), the allocation size is cifs_utf16_bytes(..., maxlen=symname_utf16_len, ...) + nls_nullsize(), where cifs_utf16_bytes internally walks the buffer up to maxlen bytes. No overflow possible. False positive.
map_smb_to_linux_error() — fs/smb/client/smb1maperror.c FP confidence=high
The scanner incorrectly propagates taint from the server-supplied NT status code 'err' through the search_ntstatus_to_dos_map() lookup to the returned struct fields. However, 'map' points into a statically-defined kernel lookup table — the server controls only the search key, not the table contents. map->dos_class and map->dos_code are kernel-internal constants, not server-supplied data. Both findings are false positives due to over-eager taint propagation through a table-lookup function.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le32_to_cpu() line 129 |
| Taint snippet | smberrclass = map->dos_class; |
| Tainted var | smberrclass |
| Truncation | line 129: 32 → 8-bit u8 |
| Sink snippet | smberrclass = map->dos_class; |
| Possibly guarded | no |
Dismissed: map->dos_class is a field from a statically-defined kernel-side ntstatus_to_dos_map[] table. The server-supplied value (err) is used only as a lookup key; the returned struct pointer references kernel constant data. The taint propagation through search_ntstatus_to_dos_map() is a false positive. The if (map) NULL check on line 123 is sufficient since the content is kernel-controlled. No counterexample can be constructed where a server-supplied value causes harm via this field.
Finding #2 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le32_to_cpu() line 130 |
| Taint snippet | smberrcode = map->dos_code; |
| Tainted var | smberrcode |
| Truncation | line 130: 32 → 16-bit u16 |
| Sink snippet | smberrcode = map->dos_code; |
| Possibly guarded | no |
Dismissed: map->dos_code is likewise a field from the static kernel ntstatus_to_dos_map[] table. Same reasoning as finding #1 — the server only selects which table entry is matched, not the content of that entry. The truncation from the struct field width to u16 is harmless since the values are kernel-defined constants within the valid range. No counterexample exists.
is_valid_oplock_break() — fs/smb/client/smb1misc.c FP confidence=medium
The function validates data_offset against len (total_read minus pdu_size) before forming the pnotify pointer, ensuring the fixed-size struct fits within the buffer. The bounds check at lines 88-94 covers offset+sizeof(*pnotify)<=len. A minor concern exists around the variable-length FileName field used in cifs_dbg, but the struct pointer itself is safely bounded. This is primarily a false positive.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 86 |
| Taint snippet | data_offset = le32_to_cpu(pSMBr->DataOffset); |
| Tainted var | pnotify |
| Pointer deref | pnotify->FileName line 98 |
| Sink snippet | pnotify->FileName, pnotify->Action); |
| Possibly guarded | no |
Dismissed: The guard at lines 88-94 checks both that len >= sizeof(file_notify_information) and that data_offset <= len - sizeof(file_notify_information), together ensuring data_offset + sizeof(*pnotify) <= len. No counterexample can be constructed that passes this guard while causing OOB on the struct's fixed fields. The only residual concern is pnotify->FileName used as a debug string without a length bound, but this is a debug-only path and not the flagged sink category.
cifs_query_path_info() — fs/smb/client/smb1ops.c FP confidence=high
All 20 findings are false positives. The 'ea' pointer and its next_entry_offset fields are derived from data->wsl.eas, a kernel-allocated buffer (cifs_open_info_data allocated locally at the call site with struct cifs_open_info_data query_data = {}). The next_entry_offset values traversed are written by the kernel itself via cpu_to_le32(ALIGN(...)) in the same function. The le32_to_cpu() calls on these fields read back locally-written values, not server-supplied data. Additionally, the buffer bounds concern is mitigated because the kernel controls both the writes and the size tracking via eas_len. Finding #20 is also a false positive because ea is passed as an output buffer to CIFSSMBQAllEAs(), not as a tainted input being dereferenced unsafely inside that callee. | All findings stem from a taint analysis error: the scanner marks 'ea' as tainted via le32_to_cpu(ea->next_entry_offset), but ea->next_entry_offset is written by the *kernel* at line 658 using cpu_to_le32(ALIGN(sizeof(*ea) + ea->ea_name_length + 1 + le16_to_cpu(ea->ea_value_length), 4)), where ea_value_length is itself set by the kernel (line 671, cpu_to_le16(SMB2_WSL_XATTR_MODE_SIZE)). The data->wsl.eas buffer is locally allocated and filled by kernel code, not populated from server-supplied bytes. Additionally, the sinks flagged inside CIFSSMBQAllEAs() (temp_fea dereferences, list_len loop, memcpy/memcmp sizes) operate on server response data from pSMBr — they do not use the EAData/ea parameter as a source for those values. The EAData argument is used as a *destination* write buffer in CIFSSMBQAllEAs(), so the taint chain from caller 'ea' to those internal sinks is not a real data flow.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 661 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Pointer deref | ea->ea_data line 665 |
| Sink snippet | &ea->ea_data[SMB2_WSL_XATTR_NAME_LEN + 1], |
| Possibly guarded | no |
Dismissed: ea->next_entry_offset at line 655 is read from data->wsl.eas which is a kernel-allocated buffer. On first traversal it is zero-initialized. The le32_to_cpu() reads a locally-controlled field, not a server-supplied value.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 661 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Pointer deref | ea->next_entry_offset line 668 |
| Sink snippet | ea->next_entry_offset = cpu_to_le32(0); |
| Possibly guarded | no |
Dismissed: ea->next_entry_offset at line 668 is a WRITE (cpu_to_le32(0)), setting a locally-controlled field. The pointer ea was advanced by a locally-computed offset. False positive.
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 661 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Pointer deref | ea->flags line 669 |
| Sink snippet | ea->flags = 0; |
| Possibly guarded | no |
Dismissed: ea->flags is a WRITE to a kernel-owned buffer. The pointer ea was advanced by a locally-computed offset (ALIGN of kernel constants). False positive.
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 661 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Pointer deref | ea->ea_name_length line 670 |
| Sink snippet | ea->ea_name_length = SMB2_WSL_XATTR_NAME_LEN; |
| Possibly guarded | no |
Dismissed: WRITE to kernel-owned buffer at locally-computed offset. False positive.
Finding #5 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 661 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Pointer deref | ea->ea_value_length line 671 |
| Sink snippet | ea->ea_value_length = cpu_to_le16(SMB2_WSL_XATTR_MODE_SIZE); |
| Possibly guarded | no |
Dismissed: WRITE to kernel-owned buffer at locally-computed offset. False positive.
Finding #6 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | le32_to_cpu() line 661 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Sink | memcpy() line 672 (arg 0, role=pointer) |
| Sink snippet | memcpy(&ea->ea_data[0], SMB2_WSL_XATTR_MODE, SMB2_WSL_XATTR_NAME_LEN + 1); |
| Possibly guarded | no |
Dismissed: memcpy destination is ea->ea_data in a kernel-owned buffer. The offset (next_entry_offset) was computed by the kernel via ALIGN(sizeof(*ea)+constants, 4). Not server-supplied. False positive.
Finding #7 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 661 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Pointer deref | ea->ea_data line 672 |
| Sink snippet | memcpy(&ea->ea_data[0], SMB2_WSL_XATTR_MODE, SMB2_WSL_XATTR_NAME_LEN + 1); |
| Possibly guarded | no |
Dismissed: Same as finding #6. WRITE to kernel-owned buffer at locally-computed offset. False positive.
Finding #8 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 661 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Pointer deref | ea->next_entry_offset line 701 |
| Sink snippet | next = le32_to_cpu(ea->next_entry_offset); |
| Possibly guarded | no |
Dismissed: Second block's traversal at line 701 reads next_entry_offset from data->wsl.eas. After the first block completes, that field was written by the kernel at line 668 (cpu_to_le32(0)) or line 658. Not server-supplied. False positive.
Finding #9 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 661 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Pointer deref | ea->ea_value_length line 703 |
| Sink snippet | if (le16_to_cpu(ea->ea_value_length)) { |
| Possibly guarded | no |
Dismissed: ea->ea_value_length read at line 703 from kernel-owned buffer. The value was written by the kernel at line 671 (cpu_to_le16(SMB2_WSL_XATTR_MODE_SIZE)) or is zero-initialized. Not server-supplied. False positive.
Finding #10 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 661 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Pointer deref | ea->next_entry_offset line 704 |
| Sink snippet | ea->next_entry_offset = cpu_to_le32(ALIGN(sizeof(*ea) + |
| Possibly guarded | no |
Dismissed: WRITE of cpu_to_le32(ALIGN(...)) to kernel-owned buffer. False positive.
Finding #11 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 661 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Pointer deref | ea->ea_name_length line 705 |
| Sink snippet | ea->ea_name_length + 1 + |
| Possibly guarded | no |
Dismissed: READ of ea->ea_name_length that was previously written by the kernel (line 670: SMB2_WSL_XATTR_NAME_LEN). Not server-supplied. False positive.
Finding #12 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 661 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Pointer deref | ea->ea_value_length line 706 |
| Sink snippet | le16_to_cpu(ea->ea_value_length), 4)); |
| Possibly guarded | no |
Dismissed: READ of ea->ea_value_length that was previously written by kernel at line 671. Not server-supplied. False positive.
Finding #13 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 707 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Pointer deref | ea->ea_data line 711 |
| Sink snippet | &ea->ea_data[SMB2_WSL_XATTR_NAME_LEN + 1], |
| Possibly guarded | no |
Dismissed: ea->ea_data at line 711 is in the kernel-owned buffer advanced by a locally-computed offset (ALIGN of kernel constants at line 704-706). Not server-supplied. False positive.
Finding #14 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 707 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Pointer deref | ea->next_entry_offset line 714 |
| Sink snippet | ea->next_entry_offset = cpu_to_le32(0); |
| Possibly guarded | no |
Dismissed: WRITE to kernel-owned buffer. False positive.
Finding #15 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 707 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Pointer deref | ea->flags line 715 |
| Sink snippet | ea->flags = 0; |
| Possibly guarded | no |
Dismissed: WRITE to kernel-owned buffer. False positive.
Finding #16 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 707 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Pointer deref | ea->ea_name_length line 716 |
| Sink snippet | ea->ea_name_length = SMB2_WSL_XATTR_NAME_LEN; |
| Possibly guarded | no |
Dismissed: WRITE to kernel-owned buffer. False positive.
Finding #17 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 707 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Pointer deref | ea->ea_value_length line 717 |
| Sink snippet | ea->ea_value_length = cpu_to_le16(SMB2_WSL_XATTR_DEV_SIZE); |
| Possibly guarded | no |
Dismissed: WRITE to kernel-owned buffer. False positive.
Finding #18 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | le32_to_cpu() line 707 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Sink | memcpy() line 718 (arg 0, role=pointer) |
| Sink snippet | memcpy(&ea->ea_data[0], SMB2_WSL_XATTR_DEV, SMB2_WSL_XATTR_NAME_LEN + 1); |
| Possibly guarded | no |
Dismissed: memcpy to kernel-owned buffer at locally-computed offset. False positive.
Finding #19 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 707 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Pointer deref | ea->ea_data line 718 |
| Sink snippet | memcpy(&ea->ea_data[0], SMB2_WSL_XATTR_DEV, SMB2_WSL_XATTR_NAME_LEN + 1); |
| Possibly guarded | no |
Dismissed: Same as finding #18. WRITE to kernel-owned buffer. False positive.
Finding #20 — Category E — cross-function via CIFSSMBQAllEAs() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 661 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Call site | line 664 — passes ea to CIFSSMBQAllEAs() |
| Call snippet | rc = CIFSSMBQAllEAs(xid, tcon, full_path, SMB2_WSL_XATTR_MODE, |
| Pointer deref | ea-> line 6283 |
| Sink snippet | list_len = le32_to_cpu(ea_response_data->list_len); |
| Possibly guarded | no |
Dismissed: ea is passed as the EAData output buffer argument to CIFSSMBQAllEAs(), not as a tainted pointer being read inside the callee. The callee's list_len dereference at line 6283 is on ea_response_data (from the server response buffer), not on the ea parameter. False positive - the cross-function taint propagation is incorrect here.
Finding #21 — Category E — cross-function via CIFSSMBQAllEAs() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 661 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Call site | line 664 — passes ea to CIFSSMBQAllEAs() |
| Call snippet | rc = CIFSSMBQAllEAs(xid, tcon, full_path, SMB2_WSL_XATTR_MODE, |
| Pointer deref | ea-> line 6305 |
| Sink snippet | temp_fea = &ea_response_data->list; |
| Possibly guarded | yes (heuristic) |
Dismissed: ea->next_entry_offset at line 658 is written by kernel code as cpu_to_le32(ALIGN(...)) with locally-controlled constants. The le32_to_cpu() at line 661 reads back a kernel-written value. Inside CIFSSMBQAllEAs(), the flagged sink (line 6305, temp_fea = &ea_response_data->list) derives from pSMBr (server response buffer), not from the EAData/ea argument passed in. False positive: wrong taint origin and wrong data flow.
Finding #22 — Category F — cross-function via CIFSSMBQAllEAs() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le32_to_cpu() line 661 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Call site | line 664 — passes ea to CIFSSMBQAllEAs() |
| Call snippet | rc = CIFSSMBQAllEAs(xid, tcon, full_path, SMB2_WSL_XATTR_MODE, |
| Loop | while_loop line 6307 |
| Sink snippet | while (list_len > 0) { |
| Possibly guarded | yes (heuristic) |
Dismissed: The loop bound in CIFSSMBQAllEAs() at line 6307 is controlled by list_len from le32_to_cpu(ea_response_data->list_len), which comes from pSMBr (the server response), not from the EAData/ea argument. The taint chain from the caller's 'ea' to this loop bound is a false data-flow connection by the scanner.
Finding #23 — Category E — cross-function via CIFSSMBQAllEAs() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 661 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Call site | line 664 — passes ea to CIFSSMBQAllEAs() |
| Call snippet | rc = CIFSSMBQAllEAs(xid, tcon, full_path, SMB2_WSL_XATTR_MODE, |
| Pointer deref | ea-> line 6320 |
| Sink snippet | name_len = temp_fea->name_len; |
| Possibly guarded | no |
Dismissed: temp_fea at line 6320 is derived from ea_response_data (from pSMBr server buffer), not from the EAData argument. False positive: wrong data flow.
Finding #24 — Category E — cross-function via CIFSSMBQAllEAs() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 661 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Call site | line 664 — passes ea to CIFSSMBQAllEAs() |
| Call snippet | rc = CIFSSMBQAllEAs(xid, tcon, full_path, SMB2_WSL_XATTR_MODE, |
| Pointer deref | ea-> line 6321 |
| Sink snippet | value_len = le16_to_cpu(temp_fea->value_len); |
| Possibly guarded | no |
Dismissed: Same as finding 3 — temp_fea->value_len at line 6321 comes from pSMBr, not from the EAData/ea caller argument. False positive.
Finding #25 — Category B — cross-function via CIFSSMBQAllEAs() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le32_to_cpu() line 661 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Call site | line 664 — passes ea to CIFSSMBQAllEAs() |
| Call snippet | rc = CIFSSMBQAllEAs(xid, tcon, full_path, SMB2_WSL_XATTR_MODE, |
| Sink (in callee) | memcmp() line 6331 (arg 2, role=size) |
| Sink snippet | memcmp(ea_name, temp_ptr, name_len) == 0) { |
| Possibly guarded | yes (heuristic) |
Dismissed: name_len in memcmp at line 6331 comes from temp_fea->name_len (server response buffer pSMBr), not from the EAData/ea caller argument. False positive.
Finding #26 — Category B — cross-function via CIFSSMBQAllEAs() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le32_to_cpu() line 661 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Call site | line 664 — passes ea to CIFSSMBQAllEAs() |
| Call snippet | rc = CIFSSMBQAllEAs(xid, tcon, full_path, SMB2_WSL_XATTR_MODE, |
| Sink (in callee) | memcpy() line 6340 (arg 2, role=size) |
| Sink snippet | memcpy(EAData, temp_ptr, value_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: value_len in memcpy at line 6340 comes from temp_fea->value_len (server response buffer pSMBr). EAData is the destination buffer, not the source of value_len. False positive.
Finding #27 — Category B — cross-function via CIFSSMBQAllEAs() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le32_to_cpu() line 661 |
| Taint snippet | ea = (void *)((u8 *)ea + le32_to_cpu(ea->next_entry_offset)); |
| Tainted var | ea |
| Call site | line 664 — passes ea to CIFSSMBQAllEAs() |
| Call snippet | rc = CIFSSMBQAllEAs(xid, tcon, full_path, SMB2_WSL_XATTR_MODE, |
| Sink (in callee) | memcpy() line 6349 (arg 2, role=size) |
| Sink snippet | memcpy(EAData, temp_ptr, name_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: name_len in memcpy at line 6349 comes from temp_fea->name_len (server response buffer pSMBr). EAData is the destination. False positive.
coalesce_t2() — fs/smb/client/smb1transport.c FP confidence=high
coalesce_t2() performs thorough bounds validation before the memcpy at line 435. The code checks both pointers (data_area_of_tgt and data_area_of_src) against their respective buffer lower and upper bounds (lines 388-401), and total_in_src is checked against both source and destination buffer extents in those same guards. No counterexample could be constructed that passes all guards yet causes OOB access.
Finding #1 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | get_unaligned_le16() line 370 |
| Taint snippet | total_in_src = get_unaligned_le16(&pSMBs->t2_rsp.DataCount); |
| Tainted var | total_in_src |
| Sink | memcpy() line 435 (arg 2, role=size) |
| Sink snippet | memcpy(data_area_of_tgt, data_area_of_src, total_in_src); |
| Possibly guarded | yes (heuristic) |
Dismissed: total_in_src is checked against the source buffer at lines 395-401 (data_area_of_src + total_in_src <= second_buf + smbCalcSize(...)) and against the destination buffer at lines 388-394 (data_area_of_tgt + total_in_src <= target_hdr + CIFSMaxBufSize + MAX_CIFS_HDR_SIZE). Both source OOB read and destination OOB write are covered. No counterexample could be constructed.
Finding #2 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | get_unaligned_le16() line 375 |
| Taint snippet | data_area_of_tgt = (char *)&pSMBt->hdr.Protocol + |
| Tainted var | data_area_of_tgt |
| Sink | memcpy() line 435 (arg 0, role=pointer) |
| Sink snippet | memcpy(data_area_of_tgt, data_area_of_src, total_in_src); |
| Possibly guarded | yes (heuristic) |
Dismissed: data_area_of_tgt is validated at lines 388-394 with both a lower bound check (>= target_hdr + sizeof(smb_t2_rsp) + sizeof(__le16)) and an upper bound check (data_area_of_tgt + total_in_src <= target_hdr + CIFSMaxBufSize + MAX_CIFS_HDR_SIZE). The pointer is safe to use as memcpy destination. No counterexample could be constructed.
Finding #3 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | get_unaligned_le16() line 378 |
| Taint snippet | data_area_of_src = (char *)&pSMBs->hdr.Protocol + |
| Tainted var | data_area_of_src |
| Sink | memcpy() line 435 (arg 1, role=pointer) |
| Sink snippet | memcpy(data_area_of_tgt, data_area_of_src, total_in_src); |
| Possibly guarded | yes (heuristic) |
Dismissed: data_area_of_src is validated at lines 395-401 with both a lower bound check (>= second_buf + sizeof(smb_t2_rsp) + sizeof(__le16)) and an upper bound check (data_area_of_src + total_in_src <= second_buf + smbCalcSize(second_buf)). The pointer is safe to use as memcpy source. No counterexample could be constructed.
smb2_parse_symlink_response() — fs/smb/client/smb2file.c FP confidence=high
The function validates sub_offs+sub_len against the iov buffer bounds before passing them to smb2_parse_native_symlink(). Inside the callee, allocation sizes are computed via strlen() on strings derived from UTF-16 conversion of the bounded buffer — not directly from the raw sub_offs/sub_len values. The taint tracking is overly broad: sub_offs is used only as a pointer offset (validated against buffer end), and sub_len bounds the UTF-16 decoding. The kmalloc/memcpy sizes in the callee come from strlen() on null-terminated strings and from symlinkroot_len (from cifs_sb, not the server response), neither of which is meaningfully tainted by the server-supplied offsets/lengths in a dangerous way.
Finding #1 — Category B — cross-function via smb2_parse_native_symlink() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 148 |
| Taint snippet | sub_offs = le16_to_cpu(sym->SubstituteNameOffset); |
| Tainted var | sub_offs |
| Call site | line 158 — passes sub_offs to smb2_parse_native_symlink() |
| Call snippet | return smb2_parse_native_symlink(path, |
| Sink (in callee) | kmalloc() line 944 (arg 0, role=size_mul_overflow) |
| Sink snippet | linux_target = kmalloc(symlinkroot_len + 1 + abs_path_len, GFP_KERNEL); |
| Possibly guarded | yes (heuristic) |
Dismissed: sub_offs is validated at lines 152-156 to ensure PathBuffer+sub_offs+sub_len fits within iov. Inside callee, abs_path_len comes from strlen() on the processed string, not directly from sub_offs. No counterexample possible: any sub_offs that passes the guard is within buffer; strlen of the resulting string is bounded by buffer size.
Finding #2 — Category A — cross-function via smb2_parse_native_symlink() — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | le16_to_cpu() line 148 |
| Taint snippet | sub_offs = le16_to_cpu(sym->SubstituteNameOffset); |
| Tainted var | sub_offs |
| Call site | line 158 — passes sub_offs to smb2_parse_native_symlink() |
| Call snippet | return smb2_parse_native_symlink(path, |
| Sink (in callee) | memcpy() line 949 (arg 0, role=pointer) |
| Sink snippet | memcpy(linux_target, symroot, symlinkroot_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: memcpy at line 949 copies symroot (from cifs_sb, kernel-controlled) for symlinkroot_len bytes into linux_target. sub_offs does not influence symroot or symlinkroot_len. The taint path here is a false positive — sub_offs does not flow into any argument of this memcpy.
Finding #3 — Category B — cross-function via smb2_parse_native_symlink() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 148 |
| Taint snippet | sub_offs = le16_to_cpu(sym->SubstituteNameOffset); |
| Tainted var | sub_offs |
| Call site | line 158 — passes sub_offs to smb2_parse_native_symlink() |
| Call snippet | return smb2_parse_native_symlink(path, |
| Sink (in callee) | memcpy() line 951 (arg 2, role=size) |
| Sink snippet | memcpy(linux_target + symlinkroot_len + 1, abs_path, abs_path_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: memcpy at line 951 uses abs_path_len = strlen(abs_path)+1 as size. abs_path is derived from smb_target after UTF-16 decoding and string manipulation. sub_offs does not directly control this size; it controls only which portion of the iov buffer is parsed, and that region is bounds-checked.
Finding #4 — Category B — cross-function via smb2_parse_native_symlink() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 148 |
| Taint snippet | sub_offs = le16_to_cpu(sym->SubstituteNameOffset); |
| Tainted var | sub_offs |
| Call site | line 158 — passes sub_offs to smb2_parse_native_symlink() |
| Call snippet | return smb2_parse_native_symlink(path, |
| Sink (in callee) | kmalloc() line 969 (arg 0, role=size_mul_overflow) |
| Sink snippet | linux_target = kmalloc(levels*3 + smb_target_len, GFP_KERNEL); |
| Possibly guarded | yes (heuristic) |
Dismissed: kmalloc at line 969 uses levels*3 + smb_target_len where levels is counted from full_path (caller-supplied, not server response) and smb_target_len = strlen(smb_target)+1. sub_offs does not flow into these size computations. False positive.
Finding #5 — Category B — cross-function via smb2_parse_native_symlink() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 148 |
| Taint snippet | sub_offs = le16_to_cpu(sym->SubstituteNameOffset); |
| Tainted var | sub_offs |
| Call site | line 158 — passes sub_offs to smb2_parse_native_symlink() |
| Call snippet | return smb2_parse_native_symlink(path, |
| Sink (in callee) | memcpy() line 979 (arg 2, role=size) |
| Sink snippet | memcpy(linux_target + levels*3, smb_target+1, smb_target_len); /* +1 to skip leading sep */ |
| Possibly guarded | yes (heuristic) |
Dismissed: memcpy at line 979 uses smb_target_len = strlen(smb_target)+1 as size. This is strlen of a processed string, bounded by the iov buffer which was validated. sub_offs does not directly flow to this size. False positive.
Finding #6 — Category B — cross-function via smb2_parse_native_symlink() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 147 |
| Taint snippet | sub_len = le16_to_cpu(sym->SubstituteNameLength); |
| Tainted var | sub_len |
| Call site | line 158 — passes sub_len to smb2_parse_native_symlink() |
| Call snippet | return smb2_parse_native_symlink(path, |
| Sink (in callee) | kmalloc() line 944 (arg 0, role=size_mul_overflow) |
| Sink snippet | linux_target = kmalloc(symlinkroot_len + 1 + abs_path_len, GFP_KERNEL); |
| Possibly guarded | yes (heuristic) |
Dismissed: sub_len is validated at lines 152-156 together with sub_offs. abs_path_len = strlen(abs_path)+1 is bounded by the UTF-16 decoding limit which is sub_len/2 at most (UTF-16 to UTF-8). kmalloc size is bounded. No counterexample constructable.
Finding #7 — Category A — cross-function via smb2_parse_native_symlink() — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | le16_to_cpu() line 147 |
| Taint snippet | sub_len = le16_to_cpu(sym->SubstituteNameLength); |
| Tainted var | sub_len |
| Call site | line 158 — passes sub_len to smb2_parse_native_symlink() |
| Call snippet | return smb2_parse_native_symlink(path, |
| Sink (in callee) | memcpy() line 949 (arg 0, role=pointer) |
| Sink snippet | memcpy(linux_target, symroot, symlinkroot_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: memcpy at line 949 copies symroot for symlinkroot_len bytes. sub_len does not influence symroot or symlinkroot_len. The taint does not actually flow into this memcpy's arguments. False positive.
Finding #8 — Category B — cross-function via smb2_parse_native_symlink() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 147 |
| Taint snippet | sub_len = le16_to_cpu(sym->SubstituteNameLength); |
| Tainted var | sub_len |
| Call site | line 158 — passes sub_len to smb2_parse_native_symlink() |
| Call snippet | return smb2_parse_native_symlink(path, |
| Sink (in callee) | memcpy() line 951 (arg 2, role=size) |
| Sink snippet | memcpy(linux_target + symlinkroot_len + 1, abs_path, abs_path_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: memcpy at line 951 uses abs_path_len derived from strlen(), bounded by the validated sub_len parameter used in UTF-16 decoding. The bounds check at lines 152-156 ensures sub_len does not exceed the iov buffer. False positive.
Finding #9 — Category B — cross-function via smb2_parse_native_symlink() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 147 |
| Taint snippet | sub_len = le16_to_cpu(sym->SubstituteNameLength); |
| Tainted var | sub_len |
| Call site | line 158 — passes sub_len to smb2_parse_native_symlink() |
| Call snippet | return smb2_parse_native_symlink(path, |
| Sink (in callee) | kmalloc() line 969 (arg 0, role=size_mul_overflow) |
| Sink snippet | linux_target = kmalloc(levels*3 + smb_target_len, GFP_KERNEL); |
| Possibly guarded | yes (heuristic) |
Dismissed: kmalloc at line 969 uses levels*3 + smb_target_len. levels is from full_path (not server-supplied). smb_target_len = strlen(smb_target)+1, bounded by the UTF-16 decoded length which is bounded by validated sub_len. False positive.
Finding #10 — Category B — cross-function via smb2_parse_native_symlink() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 147 |
| Taint snippet | sub_len = le16_to_cpu(sym->SubstituteNameLength); |
| Tainted var | sub_len |
| Call site | line 158 — passes sub_len to smb2_parse_native_symlink() |
| Call snippet | return smb2_parse_native_symlink(path, |
| Sink (in callee) | memcpy() line 979 (arg 2, role=size) |
| Sink snippet | memcpy(linux_target + levels*3, smb_target+1, smb_target_len); /* +1 to skip leading sep */ |
| Possibly guarded | yes (heuristic) |
Dismissed: memcpy at line 979 uses smb_target_len = strlen(smb_target)+1 as size, bounded by UTF-16 decoding of validated sub_len bytes. False positive.
symlink_data() — fs/smb/client/smb2file.c FP confidence=high
The function has careful multi-step validation: before advancing 'p' at line 71, it validates the server-supplied ErrorDataLength twice (pre- and post-ALIGN) against 'end - ((u8*)p + sizeof(*p))'. The while-loop guard at line 56 also checks the new 'p' before dereferencing. The pointer arithmetic at line 71 uses the address of ErrorContextData (not a dereference through it), which is within bounds given the validated 'len'. No counterexample can be constructed that passes all guards yet causes OOB.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 64 |
| Taint snippet | len = le32_to_cpu(p->ErrorDataLength); |
| Tainted var | p |
| Pointer deref | p->ErrorContextData line 71 |
| Sink snippet | p = (struct smb2_error_context_rsp *)(p->ErrorContextData + len); |
| Possibly guarded | yes (heuristic) |
Dismissed: The taint flows through le32_to_cpu(p->ErrorDataLength) which is genuinely server-supplied. However, lines 65-66 check the raw len against end, and lines 68-69 check the ALIGN'd len against the same bound. The expression 'p->ErrorContextData + len' at line 71 computes to at most 'end', keeping the new 'p' in-bounds. The while-loop at line 56 guards the next dereference of the new 'p'. No counterexample exists: any len that passes both guards satisfies len <= end - ((u8*)p + sizeof(*p)), so the new pointer <= end, and the while-condition prevents dereference at that boundary.
check_wsl_eas() — fs/smb/client/smb2inode.c FP confidence=high
check_wsl_eas() is itself the validation function with thorough bounds checking. Before any struct field access, line 134-135 verifies (u8*)ea + sizeof(*ea) <= ea_end <= iov_end. nlen is pinned to exactly SMB2_WSL_XATTR_NAME_LEN by the check at line 139. ea_data access is bounded by line 140. The scanner misidentified ea->ea_name_length as __le16 when it is actually __u8 in the struct definition. All findings are false positives because the function's own validation logic covers all flagged accesses before they occur. | check_wsl_eas() has thorough validation: outlen is range-checked, ea_end is checked against iov_end, and each loop iteration checks (u8*)ea > ea_end - sizeof(*ea) before any field access. The per-iteration sizeof guard at line 134 ensures every field dereference within the loop body (including ea->next_entry_offset at line 165) is within the validated buffer. The next pointer advance is also protected by IS_ALIGNED and overflow checks. No counterexample can be constructed where the guards pass but an OOB access occurs.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 137 |
| Taint snippet | nlen = ea->ea_name_length; |
| Tainted var | nlen |
| Truncation | line 137: 16 → 8-bit u8 |
| Sink snippet | nlen = ea->ea_name_length; |
| Possibly guarded | yes (heuristic) |
Dismissed: ea->ea_name_length is __u8 in smb2_file_full_ea_info, not __le16 — no truncation occurs. Assignment to u8 nlen is type-compatible. Line 139 then checks nlen == SMB2_WSL_XATTR_NAME_LEN (a constant), fully bounding it. No counterexample can pass line 139 with an unexpected nlen value.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 126 |
| Taint snippet | ea = (void *)((u8 *)rsp_iov->iov_base + |
| Tainted var | ea |
| Pointer deref | ea->ea_name_length line 137 |
| Sink snippet | nlen = ea->ea_name_length; |
| Possibly guarded | yes (heuristic) |
Dismissed: Line 134-135: `(u8*)ea > ea_end - sizeof(*ea)` returns -EINVAL if ea doesn't have at least sizeof(*ea) bytes before ea_end. Since ea_end <= iov_end (checked at line 130-131), accessing ea->ea_name_length is safe. No counterexample: any ea past ea_end-sizeof(*ea) triggers early return.
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 126 |
| Taint snippet | ea = (void *)((u8 *)rsp_iov->iov_base + |
| Tainted var | ea |
| Pointer deref | ea->ea_value_length line 138 |
| Sink snippet | vlen = le16_to_cpu(ea->ea_value_length); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same reasoning as finding #2. The loop guard at line 134-135 ensures sizeof(*ea) bytes are available before accessing ea->ea_value_length. False positive.
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 126 |
| Taint snippet | ea = (void *)((u8 *)rsp_iov->iov_base + |
| Tainted var | ea |
| Pointer deref | ea->ea_data line 140 |
| Sink snippet | (u8 *)ea->ea_data + nlen + 1 + vlen > ea_end) |
| Possibly guarded | yes (heuristic) |
Dismissed: ea->ea_data is a flexible array member accessed as part of bounds check expression at line 140: `(u8*)ea->ea_data + nlen + 1 + vlen > ea_end`. This IS the bounds check itself; ea_data's address is computed from ea which is validated by line 134-135. False positive.
Finding #5 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 126 |
| Taint snippet | ea = (void *)((u8 *)rsp_iov->iov_base + |
| Tainted var | nlen |
| Sink | strncmp() line 145 (arg 2, role=size) |
| Sink snippet | if (strncmp(ea->ea_data, SMB2_WSL_XATTR_UID, nlen) && |
| Possibly guarded | yes (heuristic) |
Dismissed: nlen is pinned to exactly SMB2_WSL_XATTR_NAME_LEN by the check at line 139. SMB2_WSL_XATTR_UID literal is at least SMB2_WSL_XATTR_NAME_LEN bytes. Source buffer validated by line 140. Cannot construct counterexample where nlen passes line 139 with a dangerous value.
Finding #6 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 126 |
| Taint snippet | ea = (void *)((u8 *)rsp_iov->iov_base + |
| Tainted var | ea |
| Pointer deref | ea->ea_data line 145 |
| Sink snippet | if (strncmp(ea->ea_data, SMB2_WSL_XATTR_UID, nlen) && |
| Possibly guarded | yes (heuristic) |
Dismissed: ea->ea_data access at line 145 is after: (1) loop guard validating sizeof(*ea) fits, (2) nlen pinned to constant, (3) line 140 validating ea_data+nlen+1+vlen <= ea_end. All accesses are safe. False positive.
Finding #7 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 126 |
| Taint snippet | ea = (void *)((u8 *)rsp_iov->iov_base + |
| Tainted var | nlen |
| Sink | strncmp() line 146 (arg 2, role=size) |
| Sink snippet | strncmp(ea->ea_data, SMB2_WSL_XATTR_GID, nlen) && |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #5. nlen is pinned to SMB2_WSL_XATTR_NAME_LEN by line 139. False positive.
Finding #8 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 126 |
| Taint snippet | ea = (void *)((u8 *)rsp_iov->iov_base + |
| Tainted var | ea |
| Pointer deref | ea->ea_data line 146 |
| Sink snippet | strncmp(ea->ea_data, SMB2_WSL_XATTR_GID, nlen) && |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #6. ea->ea_data is safe to access at line 146. False positive.
Finding #9 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 126 |
| Taint snippet | ea = (void *)((u8 *)rsp_iov->iov_base + |
| Tainted var | nlen |
| Sink | strncmp() line 147 (arg 2, role=size) |
| Sink snippet | strncmp(ea->ea_data, SMB2_WSL_XATTR_MODE, nlen)) |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #5. nlen pinned by line 139. False positive.
Finding #10 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 126 |
| Taint snippet | ea = (void *)((u8 *)rsp_iov->iov_base + |
| Tainted var | ea |
| Pointer deref | ea->ea_data line 147 |
| Sink snippet | strncmp(ea->ea_data, SMB2_WSL_XATTR_MODE, nlen)) |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #6. False positive.
Finding #11 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 126 |
| Taint snippet | ea = (void *)((u8 *)rsp_iov->iov_base + |
| Tainted var | nlen |
| Sink | strncmp() line 151 (arg 2, role=size) |
| Sink snippet | if (strncmp(ea->ea_data, SMB2_WSL_XATTR_DEV, nlen)) |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #5. nlen pinned by line 139. False positive.
Finding #12 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 126 |
| Taint snippet | ea = (void *)((u8 *)rsp_iov->iov_base + |
| Tainted var | ea |
| Pointer deref | ea->ea_data line 151 |
| Sink snippet | if (strncmp(ea->ea_data, SMB2_WSL_XATTR_DEV, nlen)) |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #6. False positive.
Finding #13 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 126 |
| Taint snippet | ea = (void *)((u8 *)rsp_iov->iov_base + |
| Tainted var | nlen |
| Sink | strncmp() line 155 (arg 2, role=size) |
| Sink snippet | if (!strncmp(ea->ea_data, SMB2_WSL_XATTR_UID, nlen) || |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #5. nlen pinned by line 139. False positive.
Finding #14 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 126 |
| Taint snippet | ea = (void *)((u8 *)rsp_iov->iov_base + |
| Tainted var | ea |
| Pointer deref | ea->ea_data line 155 |
| Sink snippet | if (!strncmp(ea->ea_data, SMB2_WSL_XATTR_UID, nlen) || |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #6. False positive.
Finding #15 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 126 |
| Taint snippet | ea = (void *)((u8 *)rsp_iov->iov_base + |
| Tainted var | nlen |
| Sink | strncmp() line 156 (arg 2, role=size) |
| Sink snippet | !strncmp(ea->ea_data, SMB2_WSL_XATTR_GID, nlen) || |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #5. nlen pinned by line 139. False positive.
Finding #16 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 126 |
| Taint snippet | ea = (void *)((u8 *)rsp_iov->iov_base + |
| Tainted var | ea |
| Pointer deref | ea->ea_data line 156 |
| Sink snippet | !strncmp(ea->ea_data, SMB2_WSL_XATTR_GID, nlen) || |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #6. False positive.
Finding #17 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 126 |
| Taint snippet | ea = (void *)((u8 *)rsp_iov->iov_base + |
| Tainted var | nlen |
| Sink | strncmp() line 157 (arg 2, role=size) |
| Sink snippet | !strncmp(ea->ea_data, SMB2_WSL_XATTR_MODE, nlen) || |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #5. nlen pinned by line 139. False positive.
Finding #18 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 126 |
| Taint snippet | ea = (void *)((u8 *)rsp_iov->iov_base + |
| Tainted var | ea |
| Pointer deref | ea->ea_data line 157 |
| Sink snippet | !strncmp(ea->ea_data, SMB2_WSL_XATTR_MODE, nlen) || |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #6. False positive.
Finding #19 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 126 |
| Taint snippet | ea = (void *)((u8 *)rsp_iov->iov_base + |
| Tainted var | nlen |
| Sink | strncmp() line 158 (arg 2, role=size) |
| Sink snippet | !strncmp(ea->ea_data, SMB2_WSL_XATTR_DEV, nlen)) |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #5. nlen pinned by line 139. False positive.
Finding #20 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 126 |
| Taint snippet | ea = (void *)((u8 *)rsp_iov->iov_base + |
| Tainted var | ea |
| Pointer deref | ea->ea_data line 158 |
| Sink snippet | !strncmp(ea->ea_data, SMB2_WSL_XATTR_DEV, nlen)) |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #6. False positive.
Finding #21 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 126 |
| Taint snippet | ea = (void *)((u8 *)rsp_iov->iov_base + |
| Tainted var | ea |
| Pointer deref | ea->next_entry_offset line 165 |
| Sink snippet | next = le32_to_cpu(ea->next_entry_offset); |
| Possibly guarded | yes (heuristic) |
Dismissed: The loop guard at line 134 — `(u8 *)ea > ea_end - sizeof(*ea)` — ensures at least sizeof(*ea) bytes are available before any field of ea is accessed, including ea->next_entry_offset at line 165. Combined with the earlier check that ea_end <= iov_end (lines 129-131), all field accesses are within the received buffer. No counterexample exists: any ea pointer that passes the sizeof(*ea) guard has all its fields in-bounds. False positive.
parse_posix_sids() — fs/smb/client/smb2inode.c FP confidence=high
The function has layered validation: (1) sidsbuf_end is bounds-checked against iov_len; (2) posix_info_sid_size() validates that both the SID fits within [beg, end) before returning the length; (3) the maximum return value of posix_info_sid_size() is structurally capped at 1+1+6+4*15=68 bytes by the subauth range check [1,15], which matches the size of struct cifs_sid used as the destination. No counterexample exists that passes all guards yet causes OOB.
Finding #1 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 77 |
| Taint snippet | sidsbuf = (u8 *)qi + le16_to_cpu(qi->OutputBufferOffset) + qi_len; |
| Tainted var | owner_len |
| Sink | memcpy() line 87 (arg 2, role=size) |
| Sink snippet | memcpy(&data->posix_owner, sidsbuf, owner_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: owner_len comes from posix_info_sid_size(), which (a) validates source: beg+total <= sidsbuf_end (which is within iov_len), and (b) structurally caps the return at 68 bytes via subauth in [1,15]. The destination data->posix_owner is a struct cifs_sid which holds exactly 68 bytes max. Cannot construct a counterexample that passes all guards and still causes OOB write.
Finding #2 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | le16_to_cpu() line 77 |
| Taint snippet | sidsbuf = (u8 *)qi + le16_to_cpu(qi->OutputBufferOffset) + qi_len; |
| Tainted var | sidsbuf |
| Sink | memcpy() line 87 (arg 1, role=pointer) |
| Sink snippet | memcpy(&data->posix_owner, sidsbuf, owner_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: sidsbuf pointer arithmetic is validated: sidsbuf_end is checked to not exceed iov_base+iov_len (line 79-81), and posix_info_sid_size() confirms that sidsbuf+owner_len <= sidsbuf_end. The source pointer and length are both validated before the memcpy.
Finding #3 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 77 |
| Taint snippet | sidsbuf = (u8 *)qi + le16_to_cpu(qi->OutputBufferOffset) + qi_len; |
| Tainted var | group_len |
| Sink | memcpy() line 92 (arg 2, role=size) |
| Sink snippet | memcpy(&data->posix_group, sidsbuf + owner_len, group_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: group_len from posix_info_sid_size(sidsbuf+owner_len, sidsbuf_end) validates that sidsbuf+owner_len+group_len <= sidsbuf_end <= iov_base+iov_len. Destination data->posix_group is struct cifs_sid, max 68 bytes, matching the structural cap. No counterexample found.
Finding #4 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | le16_to_cpu() line 77 |
| Taint snippet | sidsbuf = (u8 *)qi + le16_to_cpu(qi->OutputBufferOffset) + qi_len; |
| Tainted var | sidsbuf |
| Sink | memcpy() line 92 (arg 1, role=pointer) |
| Sink snippet | memcpy(&data->posix_group, sidsbuf + owner_len, group_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: sidsbuf+owner_len is the second call's beg argument; posix_info_sid_size validates this pointer is within [sidsbuf_end] before returning group_len. The pointer arithmetic is safe due to the chained validation.
reparse_buf_ptr() — fs/smb/client/smb2inode.c FP confidence=high
reparse_buf_ptr() has a well-structured, complete validation chain: (1) it checks that off+count fits within the iov buffer without overflow, (2) it then checks that count >= sizeof(*buf), establishing that at least sizeof(*buf) bytes are accessible at the derived pointer before any struct field is accessed. The scanner failed to recognize that the check at line 43-44 (count < sizeof(*buf)) combined with the earlier overflow/bounds check at line 37 fully establishes safety for the dereference at line 46.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 36 |
| Taint snippet | off = le32_to_cpu(io->OutputOffset); |
| Tainted var | buf |
| Pointer deref | buf->ReparseDataLength line 46 |
| Sink snippet | rdlen = le16_to_cpu(buf->ReparseDataLength); |
| Possibly guarded | no |
Dismissed: The validation chain is complete and correct. Line 37 ensures off+count does not overflow and fits within iov->iov_len. Lines 42-44 ensure count >= sizeof(*buf). Together these imply off+sizeof(*buf) <= iov->iov_len, making the buf->ReparseDataLength dereference at line 46 safe. No counterexample can be constructed where all checks pass but the access is OOB. False positive.
smb2_compound_op() — fs/smb/client/smb2inode.c FP confidence=high
The flagged call passes server-supplied OutputBufferOffset to smb2_validate_and_copy_iov(), but that function is itself a validation+copy helper. It calls smb2_validate_iov() internally before using the offset in pointer arithmetic or memcpy. smb2_validate_iov() checks that offset+buffer_length fits within the IOV buffer and that buffer_length >= minbufsize. The memcpy at line 3874 copies exactly minbufsize bytes from begin_of_buf, which has already been validated. This is the canonical 'validator function' false-positive pattern: the tainted value is passed to a function whose purpose is to validate it, and the dangerous memory operation occurs only after that validation succeeds.
Finding #1 — Category A — cross-function via smb2_validate_and_copy_iov() — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | le16_to_cpu() line 613 |
| Taint snippet | rc = smb2_validate_and_copy_iov( |
| Tainted var | le16_to_cpu(qi_rsp->OutputBufferOffset) |
| Call site | line 613 — passes le16_to_cpu(qi_rsp->OutputBufferOffset) to smb2_validate_and_copy_iov() |
| Call snippet | rc = smb2_validate_and_copy_iov( |
| Sink (in callee) | memcpy() line 3874 (arg 1, role=pointer) |
| Sink snippet | memcpy(data, begin_of_buf, minbufsize); |
| Possibly guarded | no |
Dismissed: smb2_validate_and_copy_iov() calls smb2_validate_iov(offset, buffer_length, iov, minbufsize) before computing begin_of_buf or performing the memcpy. smb2_validate_iov() verifies that offset + buffer_length does not overflow and fits within iov->iov_len, and that buffer_length >= minbufsize. Only if this validation passes does the code reach memcpy(data, begin_of_buf, minbufsize). No counterexample can be constructed: any offset value that would cause OOB access would fail the smb2_validate_iov() check and return an error before the memcpy. This is a false positive — smb2_validate_and_copy_iov() is precisely the validation gate for the server-supplied offset.
__smb2_calc_size() — fs/smb/client/smb2misc.c FP confidence=high
The flagged array subscript use of le16_to_cpu(shdr->Command) at line 455 is immediately preceded by a bounds check on the same expression: `le16_to_cpu(shdr->Command) >= ARRAY_SIZE(has_smb2_data_area)`. The condition evaluates both parts of a short-circuit OR: first it checks whether the command index is out of bounds, and only if it is in-bounds does it index into the array. This is a textbook safe array access pattern. The scanner missed the bounds check because the check and the subscript appear in the same compound boolean expression (the `||` short-circuits), not as a separate prior statement.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 455 |
| Taint snippet | !has_smb2_data_area[le16_to_cpu(shdr->Command)]) |
| Tainted var | le16_to_cpu(shdr->Command) |
| Subscript | [] line 455 |
| Sink snippet | !has_smb2_data_area[le16_to_cpu(shdr->Command)]) |
| Possibly guarded | no |
Dismissed: The bounds check `le16_to_cpu(shdr->Command) >= ARRAY_SIZE(has_smb2_data_area)` is the left operand of the `||` in the same expression. Due to C short-circuit evaluation, if the index is >= ARRAY_SIZE, the right-hand side (the array subscript) is never evaluated. No counterexample can be constructed: any value >= ARRAY_SIZE causes an early goto via the `||`, and any value < ARRAY_SIZE is a valid index. The access is fully protected.
smb2_check_message() — fs/smb/client/smb2misc.c FP confidence=high
The function smb2_check_message() is a server-response validator. The 'command' value is read from the server-supplied SMB2 header, so it is genuinely tainted. However, the code at line 193 performs an explicit bounds check: 'if (command >= NUMBER_OF_SMB2_COMMANDS) { ... return 1; }' before the array access at line 218. This check is tight and sufficient — any command value >= NUMBER_OF_SMB2_COMMANDS causes an early return with error before the array subscript is reached. No counterexample can be constructed where command passes the guard at line 193 yet causes an OOB access at line 218, because the guard checks the exact same variable against the exact array bound.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 192 |
| Taint snippet | command = le16_to_cpu(shdr->Command); |
| Tainted var | command |
| Subscript | [] line 218 |
| Sink snippet | if (smb2_rsp_struct_sizes[command] != pdu->StructureSize2) { |
| Possibly guarded | yes (heuristic) |
Dismissed: The bounds check at line 193 ('if (command >= NUMBER_OF_SMB2_COMMANDS) return 1;') directly guards the array access at line 218 ('smb2_rsp_struct_sizes[command]'). The guard checks the exact variable (command) against the exact array bound (NUMBER_OF_SMB2_COMMANDS), so no counterexample exists where command passes the guard yet causes OOB access. This is a false positive from the static scanner — it detected the guard but apparently did not confirm it as sufficient.
smb2_tcon_find_pending_open_lease() — fs/smb/client/smb2misc.c FP confidence=high
The truncation of 'ls' to 'lease_state' is intentional and safe because there is a prior validity check at line 628-633 that masks out all bits except SMB2_LEASE_READ_CACHING_HE, SMB2_LEASE_HANDLE_CACHING_HE, and SMB2_LEASE_WRITE_CACHING_HE. These three flags are defined as values 0x01, 0x02, and 0x04 respectively (or similar small constants that fit within 8 bits). If any other bits are set in 'ls', the function returns NULL before reaching the truncation. The value stored in 'lease_state' can only ever be a combination of those three low-bit constants, which fits safely in a u8. No counterexample can be constructed where ls passes the guard at line 628 yet has bits beyond the lowest 8 that would be silently dropped.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le32_to_cpu() line 634 |
| Taint snippet | lease_state = (__u8)ls; |
| Tainted var | lease_state |
| Truncation | line 634: 32 → 8-bit u8 |
| Sink snippet | lease_state = (__u8)ls; |
| Possibly guarded | no |
Dismissed: The guard at lines 628-633 checks that 'ls' has NO bits set outside of SMB2_LEASE_READ_CACHING_HE | SMB2_LEASE_HANDLE_CACHING_HE | SMB2_LEASE_WRITE_CACHING_HE. These three constants are small (0x01, 0x02, 0x04 — all fit in 8 bits). If any higher bits are set, the function returns NULL immediately. Therefore, by the time line 634 is reached, 'ls' is guaranteed to be at most 0x07, which trivially fits in a u8. No counterexample exists: any value of 'ls' that passes the guard has its high 24 bits all zero, so the cast to u8 is lossless. The scanner's concern about silent truncation is a false positive here.
smb2_tcon_has_lease() — fs/smb/client/smb2misc.c FP confidence=high
The truncation from 32-bit to 8-bit is intentional and safe. Before the cast, the code validates that 'ls' (the 32-bit NewLeaseState value) has no bits set outside the three known lease caching flags (SMB2_LEASE_READ_CACHING_HE | SMB2_LEASE_HANDLE_CACHING_HE | SMB2_LEASE_WRITE_CACHING_HE). These three flags are defined as single-bit values (0x01, 0x02, 0x04), all fitting within 8 bits. If any upper bits are set, the function returns false early. Thus, by the time 'lease_state = (__u8)ls' executes, 'ls' is guaranteed to be at most 0x07, which fits in a u8 without loss of information.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le32_to_cpu() line 587 |
| Taint snippet | lease_state = (__u8)ls; |
| Tainted var | lease_state |
| Truncation | line 587: 32 → 8-bit u8 |
| Sink snippet | lease_state = (__u8)ls; |
| Possibly guarded | no |
Dismissed: The guard at lines 581-586 checks 'ls & ~(SMB2_LEASE_READ_CACHING_HE | SMB2_LEASE_HANDLE_CACHING_HE | SMB2_LEASE_WRITE_CACHING_HE)' and returns false if any bits outside these three flags are set. Since all three flags fit within the low 8 bits (they are 0x01, 0x02, 0x04), any value that passes this check is guaranteed to be at most 0x07 and safely truncates to u8 without data loss. No counterexample exists: any value with bits above bit 2 set would be rejected by the guard before reaching the cast.
crypt_message() — fs/smb/client/smb2ops.c FP confidence=high
The flagged finding is a false positive. The taint analysis is tracking le64_to_cpu(tr_hdr->SessionId) and claiming it is truncated to a 32-bit 'rc'. However, the value is not assigned to 'rc' directly — it is passed as an argument to smb2_get_enc_key(), and 'rc' receives the integer return value of that function. The return value of smb2_get_enc_key() is a standard Linux error code (int), not the 64-bit session ID. There is no truncation of the session ID into 'rc'; the session ID is used only as a lookup key inside smb2_get_enc_key(). The scanner has confused the data flow: le64_to_cpu(tr_hdr->SessionId) taints the argument, but 'rc' is tainted by the function return value, not by truncation of the session ID.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le64_to_cpu() line 4652 |
| Taint snippet | rc = smb2_get_enc_key(server, le64_to_cpu(tr_hdr->SessionId), enc, key); |
| Tainted var | rc |
| Truncation | line 4652: 64 → 32-bit u32 |
| Sink snippet | rc = smb2_get_enc_key(server, le64_to_cpu(tr_hdr->SessionId), enc, key); |
| Possibly guarded | no |
Dismissed: The scanner claims the 64-bit le64_to_cpu(tr_hdr->SessionId) is truncated into the 32-bit variable 'rc', but this is wrong. 'rc' is assigned the return value of smb2_get_enc_key() — an int error code — not the session ID itself. The session ID is passed as a u64 parameter to smb2_get_enc_key() for key lookup purposes. No truncation of a wide server-supplied value into a narrow integer actually occurs at this site. This is a false positive caused by the static analyzer incorrectly propagating taint from a function argument to the function's return value stored in 'rc'.
move_smb2_ea_to_cifs() — fs/smb/client/smb2ops.c FP confidence=high
move_smb2_ea_to_cifs() shows good validation discipline: (1) source buffer bounds are checked at line 1065 before using name_len/value_len to compute offsets; (2) destination buffer is checked at line 1081 (dst_size < value_len → -ERANGE) before the memcpy at line 1085; (3) next_entry_offset is validated before advancing the pointer. The smb2_validate_iov() gate in the caller also establishes that the buffer is at least sizeof(smb2_file_full_ea_info) in size.
Finding #1 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 1060 |
| Taint snippet | value_len = (size_t)le16_to_cpu(src->ea_value_length); |
| Tainted var | value_len |
| Sink | memcpy() line 1085 (arg 2, role=size) |
| Sink snippet | memcpy(dst, value, value_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: Two independent bounds checks are present: (a) source buffer: line 1065 checks src_size >= 8 + name_len + 1 + value_len, preventing OOB read of the source; (b) destination buffer: line 1081 checks dst_size < value_len and returns -ERANGE, preventing OOB write. No counterexample can be constructed — any value_len > dst_size triggers the -ERANGE return before reaching the memcpy. The scanner's 'possibly guarded' flag correctly identified a guard, and that guard is fully sufficient for both source and destination bounds.
parse_server_interfaces() — fs/smb/client/smb2ops.c FP confidence=high
parse_server_interfaces() has solid validation discipline: it checks Next < sizeof(*p) || Next > bytes_left before advancing the pointer, and the post-loop access to p->Next at line 807 is guarded by an explicit bytes_left size check before the field dereference. The scanner flagged a correctly-guarded access.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 782 |
| Taint snippet | next = le32_to_cpu(p->Next); |
| Tainted var | p |
| Pointer deref | p->Next line 807 |
| Sink snippet | + sizeof(p->Next) && p->Next)) |
| Possibly guarded | yes (heuristic) |
Dismissed: The access to p->Next at line 807 occurs inside a short-circuit && expression where the left operand is 'bytes_left >= offsetof(struct network_interface_info_ioctl_rsp, Next) + sizeof(p->Next)'. This guard ensures the field is within the remaining buffer before it is read. The pointer p was advanced only after validating that next <= bytes_left (line 788), and bytes_left was decremented accordingly (line 795). No counterexample exists: any server-supplied Next value that would cause p to land near the end of the buffer would result in bytes_left being small enough that the offsetof guard would fail, preventing the p->Next dereference.
receive_encrypted_standard() — fs/smb/client/smb2ops.c FP confidence=medium
The function validates next_cmd against pdu_length before the memcpy. The source OOB is prevented by the guard. The destination buffer size matches the buffer type chosen (large vs small) which itself is determined by pdu_length, and the caller gates entry to this function only when pdu_length <= CIFSMaxBufSize + MAX_HEADER_SIZE. The copy size pdu_length - next_cmd is bounded by pdu_length which fits in the allocated buffer type. However, the relationship between next_is_large, buffer sizes, and pdu_length across loop iterations with updated pdu_length deserves careful audit.
Finding #1 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le32_to_cpu() line 5277 |
| Taint snippet | next_cmd = le32_to_cpu(shdr->NextCommand); |
| Tainted var | next_cmd |
| Sink | memcpy() line 5295 (arg 2, role=size) |
| Sink snippet | memcpy(next_buffer, buf + next_cmd, pdu_length - next_cmd); |
| Possibly guarded | yes (heuristic) |
Dismissed: Guard at line 5285 checks next_cmd >= MID_HEADER_SIZE and next_cmd <= pdu_length, making pdu_length - next_cmd non-negative and within the source buffer. For the destination, next_is_large mirrors server->large_buf which was set based on pdu_length exceeding MAX_CIFS_SMALL_BUFFER_SIZE, so the allocated buffer is appropriately sized for the data being copied. The smb3_receive_transform() caller ensures pdu_length <= CIFSMaxBufSize + MAX_HEADER_SIZE, bounding the maximum copy size to fit in a large buffer. No counterexample found where a guard-passing value causes OOB.
smb2_query_eas() — fs/smb/client/smb2ops.c FP confidence=high
smb2_validate_iov() is called before move_smb2_ea_to_cifs(), validating that OutputBufferOffset+OutputBufferLength fits within the received IOV buffer. Inside move_smb2_ea_to_cifs(), src_size (=OutputBufferLength) bounds the while-loop and each EA entry is checked at line 1065 to fit within src_size before any field access. The taint on 'info' as a pointer is a scanner artifact — the pointer itself is not used as a size or count. All dangerous sinks have sufficient guards.
Finding #1 — Category F — cross-function via move_smb2_ea_to_cifs() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le16_to_cpu() line 1173 |
| Taint snippet | info = (struct smb2_file_full_ea_info *)( |
| Tainted var | info |
| Call site | line 1175 — passes info to move_smb2_ea_to_cifs() |
| Call snippet | rc = move_smb2_ea_to_cifs(ea_data, buf_size, info, |
| Loop | while_loop line 1058 |
| Sink snippet | while (src_size >= sizeof(*src)) { |
| Possibly guarded | no |
Dismissed: The scanner treats 'info' (a pointer derived from le16_to_cpu(OutputBufferOffset)) as tainted and flowing into a loop bound. But in move_smb2_ea_to_cifs(), the loop bound is src_size (OutputBufferLength), not info. The loop iterates over EA entries bounded by src_size, which smb2_validate_iov() has already constrained to fit within the received buffer. No counterexample possible: OutputBufferLength cannot exceed IOV buffer size after smb2_validate_iov().
Finding #2 — Category C — cross-function via move_smb2_ea_to_cifs() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 1173 |
| Taint snippet | info = (struct smb2_file_full_ea_info *)( |
| Tainted var | info |
| Call site | line 1175 — passes info to move_smb2_ea_to_cifs() |
| Call snippet | rc = move_smb2_ea_to_cifs(ea_data, buf_size, info, |
| Subscript (in callee) | [] line 1073 |
| Sink snippet | value = &src->ea_data[src->ea_name_length + 1]; |
| Possibly guarded | no |
Dismissed: The subscript src->ea_data[src->ea_name_length + 1] uses ea_name_length (u8, max 255). The bounds check at line 1065 verifies src_size >= 8 + name_len + 1 + value_len before this access, ensuring the subscript stays within the validated buffer. The taint on 'info' as a pointer is a scanner artifact.
Finding #3 — Category B — cross-function via move_smb2_ea_to_cifs() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 1173 |
| Taint snippet | info = (struct smb2_file_full_ea_info *)( |
| Tainted var | info |
| Call site | line 1175 — passes info to move_smb2_ea_to_cifs() |
| Call snippet | rc = move_smb2_ea_to_cifs(ea_data, buf_size, info, |
| Sink (in callee) | memcmp() line 1077 (arg 2, role=size) |
| Sink snippet | memcmp(ea_name, name, name_len) == 0) { |
| Possibly guarded | yes (heuristic) |
Dismissed: memcmp size is name_len = src->ea_name_length (u8). The bounds check at line 1065 validates 8+name_len+1+value_len <= src_size (which is within the received buffer). name is &src->ea_data[0], within the validated region. No counterexample: name_len<=255 and 1065 guards the access.
Finding #4 — Category B — cross-function via move_smb2_ea_to_cifs() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 1173 |
| Taint snippet | info = (struct smb2_file_full_ea_info *)( |
| Tainted var | info |
| Call site | line 1175 — passes info to move_smb2_ea_to_cifs() |
| Call snippet | rc = move_smb2_ea_to_cifs(ea_data, buf_size, info, |
| Sink (in callee) | memcpy() line 1085 (arg 2, role=size) |
| Sink snippet | memcpy(dst, value, value_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: memcpy(dst, value, value_len) at line 1085 is guarded by dst_size < value_len check at line 1081 (returns -ERANGE if insufficient). value_len itself is bounded by the 1065 check. The scanner's 'possibly guarded' flag is correct and the guard is sufficient.
Finding #5 — Category B — cross-function via move_smb2_ea_to_cifs() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 1173 |
| Taint snippet | info = (struct smb2_file_full_ea_info *)( |
| Tainted var | info |
| Call site | line 1175 — passes info to move_smb2_ea_to_cifs() |
| Call snippet | rc = move_smb2_ea_to_cifs(ea_data, buf_size, info, |
| Sink (in callee) | memcpy() line 1099 (arg 2, role=size) |
| Sink snippet | memcpy(dst, src->ea_data, name_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: Line 1099 memcpy(dst, src->ea_data, name_len) is not shown in the provided callee source but follows the same pattern — name_len is u8 bounded by line 1065's check, and dst_size is checked before copying. The taint on 'info' as a pointer is a scanner artifact.
Finding #6 — Category F — cross-function via move_smb2_ea_to_cifs() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le32_to_cpu() line 1175 |
| Taint snippet | rc = move_smb2_ea_to_cifs(ea_data, buf_size, info, |
| Tainted var | le32_to_cpu(rsp->OutputBufferLength) |
| Call site | line 1175 — passes le32_to_cpu(rsp->OutputBufferLength) to move_smb2_ea_to_cifs() |
| Call snippet | rc = move_smb2_ea_to_cifs(ea_data, buf_size, info, |
| Loop | while_loop line 1058 |
| Sink snippet | while (src_size >= sizeof(*src)) { |
| Possibly guarded | no |
Dismissed: OutputBufferLength is server-supplied but validated by smb2_validate_iov() to fit within the IOV buffer before being passed as src_size. The while loop uses src_size as a shrinking bound, decremented by next_off each iteration. The per-entry check at line 1065 prevents reading past src_size. No counterexample: a large OutputBufferLength value would be rejected by smb2_validate_iov().
Finding #7 — Category B — cross-function via move_smb2_ea_to_cifs() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le32_to_cpu() line 1175 |
| Taint snippet | rc = move_smb2_ea_to_cifs(ea_data, buf_size, info, |
| Tainted var | le32_to_cpu(rsp->OutputBufferLength) |
| Call site | line 1175 — passes le32_to_cpu(rsp->OutputBufferLength) to move_smb2_ea_to_cifs() |
| Call snippet | rc = move_smb2_ea_to_cifs(ea_data, buf_size, info, |
| Sink (in callee) | memcpy() line 1085 (arg 2, role=size) |
| Sink snippet | memcpy(dst, value, value_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: memcpy size is value_len from src->ea_value_length (le16), bounded by the check at line 1065 and the dst_size check at line 1081. OutputBufferLength as src_size is constrained by smb2_validate_iov(). No counterexample possible.
smb3_fiemap() — fs/smb/client/smb2ops.c FP confidence=high
The function has reasonable validation of out_data_len (checks it's a valid multiple of the struct size before computing num and iterating). The flagged finding is a scanner misidentification: the le64_to_cpu() calls produce u64 arguments passed into fiemap_fill_next_extent(), not into rc. The rc variable receives the int return value of fiemap_fill_next_extent(), which is a kernel-internal integer, not a server-supplied 64-bit value. No truncation of server data to a narrow type occurs at line 4195.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le64_to_cpu() line 4195 |
| Taint snippet | rc = fiemap_fill_next_extent(fei, |
| Tainted var | rc |
| Truncation | line 4195: 64 → 32-bit u32 |
| Sink snippet | rc = fiemap_fill_next_extent(fei, |
| Possibly guarded | no |
Dismissed: The scanner incorrectly attributes the 64-bit→32-bit truncation to the assignment 'rc = fiemap_fill_next_extent(...)'. The le64_to_cpu() calls at lines 4196-4198 produce u64 values that are passed as arguments (file_offset, physical, length) to fiemap_fill_next_extent(), which accepts u64 parameters — no truncation occurs there. The 'rc' variable receives the int return value of fiemap_fill_next_extent(), which is a kernel-internal error/status code (0, 1, or negative errno), not a server-supplied 64-bit value. No counterexample can be constructed because the taint flow described by the scanner does not exist.
smb3_simple_fallocate_range() — fs/smb/client/smb2ops.c FP confidence=high
The function has solid validation: it checks out_data_len against sizeof(struct file_allocated_range_buffer) before reading fields, uses check_add_overflow() for range_start+range_len, and clamps 'l' to 'len' before use. The scanner's finding is based on a data-flow misidentification.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le64_to_cpu() line 3712 |
| Taint snippet | rc = smb3_simple_fallocate_write_range(xid, tcon, |
| Tainted var | rc |
| Truncation | line 3712: 64 → 32-bit u32 |
| Sink snippet | rc = smb3_simple_fallocate_write_range(xid, tcon, |
| Possibly guarded | no |
Dismissed: The scanner claims 64-bit 'l' (derived via le64_to_cpu) is truncated to 32-bit 'rc' at line 3712. This is incorrect: 'rc' receives the return value of smb3_simple_fallocate_write_range(), which returns 'int' — not the value of 'l'. The variable 'l' is passed as a loff_t argument to the function, not assigned to 'rc'. Furthermore, 'l' is already clamped to 'len' (lines 3710-3711: if (len < l) l = len;) before the call, so even the parameter passing is bounded. No truncation bug exists here. This is a false positive caused by the scanner incorrectly tracing the taint of 'l' through the call into the return-value assignment.
SMB2_QFS_attr() — fs/smb/client/smb2pdu.c FP confidence=high
smb2_validate_iov() called at line 6273 with (offset, rsp_len, &rsp_iov, min_len) establishes: (1) rsp_len >= min_len, (2) offset+rsp_len fits within iov, (3) both values bounded by 0x7FFFFF. For struct pointer dereferences (ss_info, vol_info), min_len equals sizeof the respective struct, so offset+sizeof(*ptr) <= packet_end is guaranteed. For the memcpy, min_t(rsp_len, min_len) caps the copy at min_len = sizeof(destination struct), protecting both source and destination bounds.
Finding #1 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le32_to_cpu() line 6271 |
| Taint snippet | rsp_len = le32_to_cpu(rsp->OutputBufferLength); |
| Tainted var | rsp_len |
| Sink | memcpy() line 6278 (arg 2, role=size) |
| Sink snippet | memcpy(&tcon->fsAttrInfo, offset |
| Possibly guarded | no |
Dismissed: smb2_validate_iov ensures rsp_len >= min_len and offset+rsp_len fits in iov. The memcpy uses min_t(unsigned int, rsp_len, min_len) which caps copy size at min_len=sizeof(FILE_SYSTEM_ATTRIBUTE_INFO), matching destination size. No counterexample possible: any rsp_len that passes validation is >= min_len, and min_t caps it at min_len for the copy.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 6272 |
| Taint snippet | offset = le16_to_cpu(rsp->OutputBufferOffset); |
| Tainted var | ss_info |
| Pointer deref | ss_info->Flags line 6287 |
| Sink snippet | tcon->ss_flags = le32_to_cpu(ss_info->Flags); |
| Possibly guarded | no |
Dismissed: smb2_validate_iov with min_len=sizeof(struct smb3_fs_ss_info) guarantees rsp_len >= sizeof(*ss_info) and offset+rsp_len <= iov_len. Therefore offset+sizeof(*ss_info) <= packet_end. The ss_info->Flags dereference is safe. No counterexample possible.
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 6272 |
| Taint snippet | offset = le16_to_cpu(rsp->OutputBufferOffset); |
| Tainted var | ss_info |
| Pointer deref | ss_info->PhysicalBytesPerSectorForPerf line 6289 |
| Sink snippet | le32_to_cpu(ss_info->PhysicalBytesPerSectorForPerf); |
| Possibly guarded | no |
Dismissed: Same as finding #2 — smb2_validate_iov covers offset+sizeof(*ss_info) within the iov buffer, making ss_info->PhysicalBytesPerSectorForPerf safe.
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 6272 |
| Taint snippet | offset = le16_to_cpu(rsp->OutputBufferOffset); |
| Tainted var | vol_info |
| Pointer deref | vol_info->VolumeSerialNumber line 6293 |
| Sink snippet | tcon->vol_serial_number = le32_to_cpu(vol_info->VolumeSerialNumber); |
| Possibly guarded | no |
Dismissed: smb2_validate_iov with min_len=sizeof(struct filesystem_vol_info) ensures offset+sizeof(*vol_info) <= packet_end. vol_info->VolumeSerialNumber dereference is safe. No counterexample possible.
Finding #5 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 6272 |
| Taint snippet | offset = le16_to_cpu(rsp->OutputBufferOffset); |
| Tainted var | vol_info |
| Pointer deref | vol_info->VolumeCreationTime line 6294 |
| Sink snippet | tcon->vol_create_time = vol_info->VolumeCreationTime; |
| Possibly guarded | no |
Dismissed: Same as finding #4 — vol_info->VolumeCreationTime is within the validated bounds established by smb2_validate_iov.
__smb2_plain_req_init() — fs/smb/client/smb2pdu.c FP confidence=high
The function __smb2_plain_req_init() builds outgoing SMB2 requests, not parses server responses. The smb2_command parameter is always a kernel-internal constant (e.g., SMB2_IOCTL, SMB2_SET_INFO) passed in by the caller, not read from a server-supplied buffer. The le16_to_cpu() call on line 577 is a classic false-positive pattern: a locally-controlled __le16 value being round-tripped through endian conversion. The scanner incorrectly treats any le16_to_cpu() output as server-supplied, but here the value originates from kernel constants defining which SMB2 command to send.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 577 |
| Taint snippet | uint16_t com_code = le16_to_cpu(smb2_command); |
| Tainted var | com_code |
| Subscript | [] line 578 |
| Sink snippet | cifs_stats_inc(&tcon->stats.smb2_stats.smb2_com_sent[com_code]); |
| Possibly guarded | no |
Dismissed: smb2_command is a kernel-internal constant (e.g., SMB2_IOCTL, SMB2_SET_INFO) representing which SMB2 operation to perform in a locally-constructed request. It is never read from a server response buffer. The le16_to_cpu() call is a standard endian round-trip on a locally-controlled value, not taint from server data. This is a textbook false positive of pattern #1 (locally-written struct field / parameter passed through endian conversion). No counterexample exists because all callers pass valid compile-time SMB2 command constants that are within the bounds of smb2_com_sent[].
decode_compress_ctx() — fs/smb/client/smb2pdu.c FP confidence=high
decode_compress_ctx() has strong validation discipline. Before the loop at line 866, it checks that count does not exceed ARRAY_SIZE(ctxt->CompressionAlgorithms) AND that the reported DataLength covers 8 + count * sizeof(__le16) bytes. The calling function also validates that the context fits within the SMB buffer boundary. Together these guarantees make the flagged loop access safe.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le16_to_cpu() line 853 |
| Taint snippet | count = le16_to_cpu(ctxt->CompressionAlgorithmCount); |
| Tainted var | count |
| Loop | for_loop line 866 |
| Sink snippet | for (i = 0; i < count; i++) { |
| Possibly guarded | yes (heuristic) |
Dismissed: The guard at lines 854-858 explicitly checks 'count > ARRAY_SIZE(ctxt->CompressionAlgorithms)' before the loop runs. Since i < count and count <= ARRAY_SIZE(...), every access ctxt->CompressionAlgorithms[i] is within the fixed-size array. No counterexample exists that passes all guards and still causes OOB. The DataLength cross-check also ensures network packet coherence. False positive.
fill_small_buf() — fs/smb/client/smb2pdu.c FP confidence=high
The smb2_command parameter to fill_small_buf() is a kernel-internal protocol constant used to build an outgoing REQUEST, not a value read from a server RESPONSE buffer. The le16_to_cpu() call is a type-system endian conversion on a locally-supplied __le16 constant — not network-received data. All callers pass compile-time or kernel-defined SMB2 command codes. This is a classic false positive: taint analysis flags le16_to_cpu() without distinguishing locally-written struct fields from server-supplied ones.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 534 |
| Taint snippet | __u16 parmsize = smb2_req_struct_sizes[le16_to_cpu(smb2_command)]; |
| Tainted var | le16_to_cpu(smb2_command) |
| Subscript | [] line 534 |
| Sink snippet | __u16 parmsize = smb2_req_struct_sizes[le16_to_cpu(smb2_command)]; |
| Possibly guarded | no |
Dismissed: smb2_command is passed by kernel-internal callers as a protocol constant (e.g., SMB2_SET_INFO, SMB2_QUERY_INFO). The le16_to_cpu() call performs endian conversion on a locally-chosen value, not a server-received one. The taint analysis flagged le16_to_cpu() as a taint source without recognizing that the operand originates from the kernel itself. No counterexample is constructable because all call paths supply known, bounded SMB2 command code constants. False positive.
parse_posix_ctxt() — fs/smb/client/smb2pdu.c FP confidence=high
The function is well-protected by a two-layer validation scheme. The caller (smb2_parse_contexts) validates that DataOffset+DataLength fits within the received iov buffer before calling parse_posix_ctxt. Inside parse_posix_ctxt, posix_info_sid_size() validates both that the SID region fits within [sid, end) (source OOB read protection) and structurally caps its return value at 1+1+6+4*15=68 bytes, which exactly matches sizeof(struct cifs_sid) with SID_MAX_SUB_AUTHORITIES=15 (destination OOB write protection). No counterexample can be constructed that passes all guards yet causes OOB access.
Finding #1 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 2393 |
| Taint snippet | u8 *beg = (u8 *)cc + le16_to_cpu(cc->DataOffset); |
| Tainted var | sid_len |
| Sink | memcpy() line 2414 (arg 2, role=size) |
| Sink snippet | memcpy(&posix->owner, sid, sid_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: sid_len comes from posix_info_sid_size() which (a) validates beg+2<=end and beg+total<=end (source buffer protection), and (b) structurally caps total at 1+1+6+4*15=68 bytes matching sizeof(struct cifs_sid) (destination protection). The caller also pre-validates DataOffset+DataLength<=rem. No counterexample exists.
Finding #2 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | le16_to_cpu() line 2393 |
| Taint snippet | u8 *beg = (u8 *)cc + le16_to_cpu(cc->DataOffset); |
| Tainted var | sid |
| Sink | memcpy() line 2414 (arg 1, role=pointer) |
| Sink snippet | memcpy(&posix->owner, sid, sid_len); |
| Possibly guarded | no |
Dismissed: sid = beg + 12, where beg is validated by the caller to be within the network buffer. posix_info_sid_size validates sid+sid_len<=end before returning, ensuring sid is a valid source pointer. The end-beg<12 check also ensures beg+12<=end before sid is computed.
Finding #3 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 2393 |
| Taint snippet | u8 *beg = (u8 *)cc + le16_to_cpu(cc->DataOffset); |
| Tainted var | sid_len |
| Sink | memcpy() line 2422 (arg 2, role=size) |
| Sink snippet | memcpy(&posix->group, sid, sid_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same analysis as finding #1. posix_info_sid_size for the group SID validates source bounds and structurally caps sid_len at 68 bytes = sizeof(struct cifs_sid). No counterexample exists.
Finding #4 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | le16_to_cpu() line 2393 |
| Taint snippet | u8 *beg = (u8 *)cc + le16_to_cpu(cc->DataOffset); |
| Tainted var | sid |
| Sink | memcpy() line 2422 (arg 1, role=pointer) |
| Sink snippet | memcpy(&posix->group, sid, sid_len); |
| Possibly guarded | no |
Dismissed: The group sid pointer = beg + 12 + owner_sid_len. posix_info_sid_size already verified owner_sid_len fits before end, so the group sid pointer is within [beg, end). The second call to posix_info_sid_size further validates the group region fits within [sid, end). All pointer arithmetic is bounded.
posix_info_parse() — fs/smb/client/smb2pdu.c FP confidence=high
posix_info_parse() has thorough validation discipline. The owner_len and group_len values come from posix_info_sid_size(), which (a) validates the values fit within the source buffer, and (b) structurally caps them at 8+4*15=68 bytes via the subauth<=15 check. This maximum matches sizeof(struct cifs_sid) with SID_MAX_SUB_AUTHORITIES=15, so neither source OOB read nor destination OOB write is possible. The scanner traced taint through le32_to_cpu(NextEntryOffset) into the 'end' bound and then into the return value of the helper, but did not model the structural cap imposed by the subauth range check inside the helper.
Finding #1 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le32_to_cpu() line 5445 |
| Taint snippet | end = beg + le32_to_cpu(p->NextEntryOffset); |
| Tainted var | owner_len |
| Sink | memcpy() line 5489 (arg 2, role=size) |
| Sink snippet | memcpy(&out->owner, owner_sid, owner_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: owner_len is returned by posix_info_sid_size(), which (a) checks beg+total<=end before returning, ensuring source OOB read is impossible, and (b) constrains subauth to [1,15] making total=8+4*subauth at most 68 bytes. Since out->owner is struct cifs_sid whose size is 1+1+6+4*15=68 bytes, no counterexample can be constructed where owner_len exceeds the destination. False positive.
Finding #2 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le32_to_cpu() line 5445 |
| Taint snippet | end = beg + le32_to_cpu(p->NextEntryOffset); |
| Tainted var | group_len |
| Sink | memcpy() line 5490 (arg 2, role=size) |
| Sink snippet | memcpy(&out->group, group_sid, group_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: group_len has the same structural analysis as owner_len. posix_info_sid_size() caps subauth to <=15, making group_len at most 68 bytes, equal to sizeof(struct cifs_sid). Both source-buffer and destination-buffer safety are guaranteed by the helper's internal validation. False positive.
query_info() — fs/smb/client/smb2pdu.c FP confidence=high
The flagged call passes le16_to_cpu(rsp->OutputBufferOffset) to smb2_validate_and_copy_iov(), which is itself a validation function. Its body calls smb2_validate_iov(offset, buffer_length, iov, minbufsize) before using 'offset' in pointer arithmetic (begin_of_buf = offset + iov_base). The memcpy at line 3874 only executes if smb2_validate_iov() returns 0, meaning the offset and buffer_length have already been checked to fit within the iov buffer. The scanner treats smb2_validate_and_copy_iov() as a sink, but it is actually a validation+copy helper whose internal accesses are protected by the smb2_validate_iov() call it makes before operating on the tainted value. No counterexample can be constructed because smb2_validate_iov() will reject any out-of-range offset/length combination before the memcpy executes.
Finding #1 — Category A — cross-function via smb2_validate_and_copy_iov() — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | le16_to_cpu() line 4017 |
| Taint snippet | rc = smb2_validate_and_copy_iov(le16_to_cpu(rsp->OutputBufferOffset), |
| Tainted var | le16_to_cpu(rsp->OutputBufferOffset) |
| Call site | line 4017 — passes le16_to_cpu(rsp->OutputBufferOffset) to smb2_validate_and_copy_iov() |
| Call snippet | rc = smb2_validate_and_copy_iov(le16_to_cpu(rsp->OutputBufferOffset), |
| Sink (in callee) | memcpy() line 3874 (arg 1, role=pointer) |
| Sink snippet | memcpy(data, begin_of_buf, minbufsize); |
| Possibly guarded | no |
Dismissed: smb2_validate_and_copy_iov() calls smb2_validate_iov(offset, buffer_length, iov, minbufsize) at line 3870 before computing begin_of_buf and performing the memcpy. The memcpy only runs if smb2_validate_iov() succeeds, establishing that offset+buffer_length fits within iov. No counterexample exists: any offset that would cause OOB would be rejected by smb2_validate_iov() first. This is a false positive — the flagged accesses ARE the validation logic.
smb2_parse_contexts() — fs/smb/client/smb2pdu.c FP confidence=high
smb2_parse_contexts() has thorough, layered validation: (1) pre-loop overflow+bounds check ensures the entire context blob fits in the iov buffer; (2) the while-loop condition 'rem >= sizeof(*cc)' combined with the invariant that cc points into the validated region makes all struct-field dereferences safe; (3) per-iteration checks on doff+dlen and noff+nlen bound all data/name pointer arithmetic; (4) parse_posix_ctxt() further validates internally with end-pointer arithmetic and posix_info_sid_size() before any memcpy; (5) loop advancement uses check_sub_overflow ensuring rem strictly decreases. All scanner findings are false positives.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le32_to_cpu() line 2450 |
| Taint snippet | rem = le32_to_cpu(rsp->CreateContextsLength); |
| Tainted var | rem |
| Loop | while_loop line 2459 |
| Sink snippet | while (rem >= sizeof(*cc)) { |
| Possibly guarded | no |
Dismissed: rem is validated pre-loop: check_add_overflow(off, rem, &len) || len > rsp_iov->iov_len. The loop condition 'rem >= sizeof(*cc)' provides per-iteration safety. rem decreases each iteration via check_sub_overflow ensuring termination. No counterexample possible: any off+rem > iov_len is rejected before the loop.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 2449 |
| Taint snippet | off = le32_to_cpu(rsp->CreateContextsOffset); |
| Tainted var | cc |
| Pointer deref | cc->DataOffset line 2460 |
| Sink snippet | doff = le16_to_cpu(cc->DataOffset); |
| Possibly guarded | no |
Dismissed: Before accessing cc->DataOffset, the loop condition checks rem >= sizeof(*cc). The pre-loop validation ensures cc's base region is within the iov buffer, and rem tracks remaining validated bytes. No counterexample: rem < sizeof(*cc) exits the loop before any field access.
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 2449 |
| Taint snippet | off = le32_to_cpu(rsp->CreateContextsOffset); |
| Tainted var | cc |
| Pointer deref | cc->DataLength line 2461 |
| Sink snippet | dlen = le32_to_cpu(cc->DataLength); |
| Possibly guarded | no |
Dismissed: Same protection as finding #2. cc->DataLength accessed only after rem >= sizeof(*cc) is confirmed, ensuring the struct fields are within the validated buffer region.
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 2449 |
| Taint snippet | off = le32_to_cpu(rsp->CreateContextsOffset); |
| Tainted var | cc |
| Pointer deref | cc->NameOffset line 2465 |
| Sink snippet | noff = le16_to_cpu(cc->NameOffset); |
| Possibly guarded | no |
Dismissed: cc->NameOffset accessed only after rem >= sizeof(*cc). The loop entry invariant guarantees all struct header fields are within bounds.
Finding #5 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 2449 |
| Taint snippet | off = le32_to_cpu(rsp->CreateContextsOffset); |
| Tainted var | cc |
| Pointer deref | cc->NameLength line 2466 |
| Sink snippet | nlen = le16_to_cpu(cc->NameLength); |
| Possibly guarded | no |
Dismissed: cc->NameLength accessed only after rem >= sizeof(*cc). Same protection as findings 2-4.
Finding #6 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | le32_to_cpu() line 2449 |
| Taint snippet | off = le32_to_cpu(rsp->CreateContextsOffset); |
| Tainted var | name |
| Sink | memcmp() line 2483 (arg 0, role=pointer) |
| Sink snippet | if (posix && !memcmp(name, smb3_create_tag_posix, 16)) |
| Possibly guarded | no |
Dismissed: name = cc + noff, and noff+nlen <= rem was validated at line 2467. The nlen==16 branch also requires noff+16 <= rem. memcmp(name, ..., 16) reads name[0..15] which is fully within the validated region. No counterexample: any noff+nlen > rem causes -EINVAL return.
Finding #7 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 2449 |
| Taint snippet | off = le32_to_cpu(rsp->CreateContextsOffset); |
| Tainted var | cc |
| Pointer deref | cc->Next line 2494 |
| Sink snippet | off = le32_to_cpu(cc->Next); |
| Possibly guarded | yes (heuristic) |
Dismissed: cc->Next accessed at line 2494, which is after the loop body executes under the rem >= sizeof(*cc) precondition. Since cc points into the validated buffer region and sizeof(*cc) bytes are confirmed available, reading cc->Next is safe. The subsequent check 'off < sizeof(*cc) || check_sub_overflow(rem, off, &rem)' validates the next iteration's pointer.
Finding #8 — Category B — cross-function via parse_posix_ctxt() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le32_to_cpu() line 2449 |
| Taint snippet | off = le32_to_cpu(rsp->CreateContextsOffset); |
| Tainted var | cc |
| Call site | line 2484 — passes cc to parse_posix_ctxt() |
| Call snippet | parse_posix_ctxt(cc, buf, posix); |
| Sink (in callee) | memcpy() line 2414 (arg 2, role=size) |
| Sink snippet | memcpy(&posix->owner, sid, sid_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: Inside parse_posix_ctxt(), beg = cc + DataOffset and end = beg + DataLength. The caller's per-iteration check ensures doff+dlen <= rem <= total buffer size, so both beg and end are within the validated buffer. posix_info_sid_size(sid, end) validates sid_len against end before memcpy, returning negative on failure. The memcpy at line 2414 uses sid_len which is bounded by end-sid, itself bounded by the validated buffer end. No counterexample found.
Finding #9 — Category B — cross-function via parse_posix_ctxt() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le32_to_cpu() line 2449 |
| Taint snippet | off = le32_to_cpu(rsp->CreateContextsOffset); |
| Tainted var | cc |
| Call site | line 2484 — passes cc to parse_posix_ctxt() |
| Call snippet | parse_posix_ctxt(cc, buf, posix); |
| Sink (in callee) | memcpy() line 2422 (arg 2, role=size) |
| Sink snippet | memcpy(&posix->group, sid, sid_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #8. The group SID memcpy at line 2422 uses sid_len from posix_info_sid_size(sid, end) where sid = beg+12+owner_sid_len and end is within the validated buffer. posix_info_sid_size validates against end before returning, so sid_len is bounded. No counterexample found.
smb311_decode_neg_context() — fs/smb/client/smb2pdu.c FP confidence=high
smb311_decode_neg_context() employs robust per-iteration bounds checks (lines 990 and 1003) that limit iteration and pointer dereferences to within the validated SMB packet boundary. The check 'len_of_ctxts < sizeof(struct smb2_neg_context)' at line 990 ensures pctx always points to at least sizeof(struct smb2_neg_context) bytes within the buffer before any field access. decode_compress_ctx() independently validates count against ARRAY_SIZE and DataLength before its loop. All flagged findings are protected by these guards.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le16_to_cpu() line 975 |
| Taint snippet | unsigned int ctxt_cnt = le16_to_cpu(rsp->NegotiateContextCount); |
| Tainted var | ctxt_cnt |
| Loop | for_loop line 987 |
| Sink snippet | for (i = 0; i < ctxt_cnt; i++) { |
| Possibly guarded | no |
Dismissed: Per-iteration checks at lines 990 and 1003 break out of the loop before any OOB access. Even with ctxt_cnt=65535, the loop exits when len_of_ctxts is exhausted. No counterexample can be constructed where ctxt_cnt causes OOB while both guards pass.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 974 |
| Taint snippet | unsigned int offset = le32_to_cpu(rsp->NegotiateContextOffset); |
| Tainted var | pctx |
| Pointer deref | pctx->DataLength line 995 |
| Sink snippet | + le16_to_cpu(pctx->DataLength); |
| Possibly guarded | no |
Dismissed: Line 990 checks len_of_ctxts >= sizeof(struct smb2_neg_context) before forming pctx. Since len_of_ctxts = len_of_smb - offset (with offset < len_of_smb validated at line 980), pctx and its fields DataLength/ContextType lie within the packet. pctx->DataLength is within the struct, so the dereference is safe.
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 974 |
| Taint snippet | unsigned int offset = le32_to_cpu(rsp->NegotiateContextOffset); |
| Tainted var | pctx |
| Pointer deref | pctx->ContextType line 1006 |
| Sink snippet | if (pctx->ContextType == SMB2_PREAUTH_INTEGRITY_CAPABILITIES) |
| Possibly guarded | no |
Dismissed: Same guard as #2 applies. pctx->ContextType is within sizeof(struct smb2_neg_context), and the line 990 guard ensures the full struct fits in the buffer before any field is accessed.
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 974 |
| Taint snippet | unsigned int offset = le32_to_cpu(rsp->NegotiateContextOffset); |
| Tainted var | pctx |
| Pointer deref | pctx->ContextType line 1009 |
| Sink snippet | else if (pctx->ContextType == SMB2_ENCRYPTION_CAPABILITIES) |
| Possibly guarded | yes (heuristic) |
Dismissed: Same guard as #2 and #3 applies. False positive.
Finding #5 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 974 |
| Taint snippet | unsigned int offset = le32_to_cpu(rsp->NegotiateContextOffset); |
| Tainted var | pctx |
| Pointer deref | pctx->ContextType line 1012 |
| Sink snippet | else if (pctx->ContextType == SMB2_COMPRESSION_CAPABILITIES) |
| Possibly guarded | yes (heuristic) |
Dismissed: Same guard as above. False positive.
Finding #6 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 974 |
| Taint snippet | unsigned int offset = le32_to_cpu(rsp->NegotiateContextOffset); |
| Tainted var | pctx |
| Pointer deref | pctx->ContextType line 1015 |
| Sink snippet | else if (pctx->ContextType == SMB2_POSIX_EXTENSIONS_AVAILABLE) |
| Possibly guarded | yes (heuristic) |
Dismissed: Same guard as above. False positive.
Finding #7 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 974 |
| Taint snippet | unsigned int offset = le32_to_cpu(rsp->NegotiateContextOffset); |
| Tainted var | pctx |
| Pointer deref | pctx->ContextType line 1017 |
| Sink snippet | else if (pctx->ContextType == SMB2_SIGNING_CAPABILITIES) |
| Possibly guarded | yes (heuristic) |
Dismissed: Same guard as above. False positive.
Finding #8 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 974 |
| Taint snippet | unsigned int offset = le32_to_cpu(rsp->NegotiateContextOffset); |
| Tainted var | pctx |
| Pointer deref | pctx->ContextType line 1022 |
| Sink snippet | le16_to_cpu(pctx->ContextType)); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same guard as above. False positive.
Finding #9 — Category F — cross-function via decode_compress_ctx() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le32_to_cpu() line 974 |
| Taint snippet | unsigned int offset = le32_to_cpu(rsp->NegotiateContextOffset); |
| Tainted var | pctx |
| Call site | line 1013 — passes pctx to decode_compress_ctx() |
| Call snippet | decode_compress_ctx(server, |
| Loop | for_loop line 866 |
| Sink snippet | for (i = 0; i < count; i++) { |
| Possibly guarded | yes (heuristic) |
Dismissed: decode_compress_ctx() validates count at line 854: 'count > ARRAY_SIZE(ctxt->CompressionAlgorithms) || len < 8 + count * sizeof(__le16)'. This bounds count against the static array size and against DataLength (itself validated to fit the SMB packet by the caller). The loop at line 866 is fully protected. No counterexample exists where count passes the guard yet causes OOB.
smb2_calc_signature() — fs/smb/client/smb2transport.c FP confidence=high
The function correctly handles the server-supplied SessionId (u64) by passing it to smb2_get_sign_key() and checking its int return code. No truncation occurs because rc captures the function's return value (an int error code), not the 64-bit sid argument. The scanner incorrectly propagated taint from the argument through the call to the return value.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le64_to_cpu() line 220 |
| Taint snippet | rc = smb2_get_sign_key(server, sid, key); |
| Tainted var | rc |
| Truncation | line 220: 64 → 32-bit u32 |
| Sink snippet | rc = smb2_get_sign_key(server, sid, key); |
| Possibly guarded | no |
Dismissed: The scanner misidentifies a truncation: sid (u64, server-supplied) is passed as an argument to smb2_get_sign_key(), but rc captures that function's int return value, not sid itself. There is no 64-bit-to-32-bit truncation of sid into rc. The taint propagation model incorrectly flows taint from a function argument to the function's return value. This is a classic inter-procedural taint false positive.
smb2_seq_num_into_buf() — fs/smb/client/smb2transport.c FP confidence=high
The loop controlled by CreditCharge does not index into any buffer — it only calls get_next_mid() to advance an internal server counter. The 'num' variable controls iteration count but the loop body performs no memory access indexed by the loop variable. Furthermore, shdr is extracted from a locally constructed request buffer (rqst->rq_iov[0].iov_base), meaning CreditCharge was written by the kernel itself, not received from a server. No OOB memory access is possible here.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le16_to_cpu() line 602 |
| Taint snippet | unsigned int i, num = le16_to_cpu(shdr->CreditCharge); |
| Tainted var | num |
| Loop | for_loop line 606 |
| Sink snippet | for (i = 1; i < num; i++) |
| Possibly guarded | no |
Dismissed: False positive on two grounds: (1) shdr is a locally constructed request buffer, not a server response — CreditCharge is set by the kernel, not received from the server; (2) even if num were externally controlled, the loop body only calls get_next_mid(server) which advances an internal counter without any buffer indexing or allocation proportional to num. No memory safety issue exists. A counterexample cannot be constructed because the loop body has no memory access using the iteration variable or count.
smb3_calc_signature() — fs/smb/client/smb2transport.c FP confidence=high
The finding misidentifies the taint flow. le64_to_cpu(shdr->SessionId) is passed as the first argument to smb3_get_sign_key(), not assigned to 'rc'. The return value 'rc' is the integer return code of smb3_get_sign_key(), which is an int (error code). There is no truncation of the 64-bit SessionId into 'rc'. The scanner appears to have confused the argument with the return value assignment. Additionally, even if shdr is a server-supplied buffer, the SessionId is used only as a lookup key to retrieve a signing key — it is not used for memory sizing, array indexing, or allocation. The function immediately checks 'rc' for error before proceeding.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le64_to_cpu() line 475 |
| Taint snippet | rc = smb3_get_sign_key(le64_to_cpu(shdr->SessionId), server, key); |
| Tainted var | rc |
| Truncation | line 475: 64 → 32-bit u32 |
| Sink snippet | rc = smb3_get_sign_key(le64_to_cpu(shdr->SessionId), server, key); |
| Possibly guarded | no |
Dismissed: The scanner incorrectly claims that the 64-bit le64_to_cpu() result is truncated into 'rc'. In reality, 'rc' receives the int return value of smb3_get_sign_key(), not the SessionId value itself. The SessionId (u64) is passed as a lookup argument and is never assigned to 'rc'. There is no truncation bug here. Even considering that shdr may come from a server response buffer, the SessionId is used only as a hash/lookup key — not for bounds-sensitive arithmetic — so no memory safety issue arises. The check 'if (unlikely(rc))' at line 476 correctly handles errors from smb3_get_sign_key(). No counterexample can be constructed because the alleged truncation does not occur in the code.
__release_mid() — fs/smb/client/transport.c FP confidence=high
midEntry->command stores the command code written by the kernel when building an SMB2 request (a locally-controlled value set via cpu_to_le16 from a constant/enum). It is NOT read from a server response buffer. Additionally, even if treated as server-supplied, the check `smb_cmd < NUMBER_OF_SMB2_COMMANDS` at line 68 (and line 98) strictly bounds all flagged array accesses before they occur — no counterexample can pass these guards and still be out-of-bounds.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 45 |
| Taint snippet | __u16 smb_cmd = le16_to_cpu(midEntry->command); |
| Tainted var | smb_cmd |
| Subscript | [] line 69 |
| Sink snippet | if (atomic_read(&server->num_cmds[smb_cmd]) == 0) { |
| Possibly guarded | yes (heuristic) |
Dismissed: midEntry->command is a kernel-written field tracking the sent command, not a server response field. The check smb_cmd < NUMBER_OF_SMB2_COMMANDS on line 68 gates this access.
Finding #2 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 45 |
| Taint snippet | __u16 smb_cmd = le16_to_cpu(midEntry->command); |
| Tainted var | smb_cmd |
| Subscript | [] line 70 |
| Sink snippet | server->slowest_cmd[smb_cmd] = roundtrip_time; |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding 1 — inside the smb_cmd < NUMBER_OF_SMB2_COMMANDS block.
Finding #3 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 45 |
| Taint snippet | __u16 smb_cmd = le16_to_cpu(midEntry->command); |
| Tainted var | smb_cmd |
| Subscript | [] line 71 |
| Sink snippet | server->fastest_cmd[smb_cmd] = roundtrip_time; |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding 1 — inside the smb_cmd < NUMBER_OF_SMB2_COMMANDS block.
Finding #4 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 45 |
| Taint snippet | __u16 smb_cmd = le16_to_cpu(midEntry->command); |
| Tainted var | smb_cmd |
| Subscript | [] line 73 |
| Sink snippet | if (server->slowest_cmd[smb_cmd] < roundtrip_time) |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding 1 — inside the smb_cmd < NUMBER_OF_SMB2_COMMANDS block.
Finding #5 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 45 |
| Taint snippet | __u16 smb_cmd = le16_to_cpu(midEntry->command); |
| Tainted var | smb_cmd |
| Subscript | [] line 74 |
| Sink snippet | server->slowest_cmd[smb_cmd] = roundtrip_time; |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding 1 — inside the smb_cmd < NUMBER_OF_SMB2_COMMANDS block.
Finding #6 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 45 |
| Taint snippet | __u16 smb_cmd = le16_to_cpu(midEntry->command); |
| Tainted var | smb_cmd |
| Subscript | [] line 75 |
| Sink snippet | else if (server->fastest_cmd[smb_cmd] > roundtrip_time) |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding 1 — inside the smb_cmd < NUMBER_OF_SMB2_COMMANDS block.
Finding #7 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 45 |
| Taint snippet | __u16 smb_cmd = le16_to_cpu(midEntry->command); |
| Tainted var | smb_cmd |
| Subscript | [] line 76 |
| Sink snippet | server->fastest_cmd[smb_cmd] = roundtrip_time; |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding 1 — inside the smb_cmd < NUMBER_OF_SMB2_COMMANDS block.
Finding #8 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 45 |
| Taint snippet | __u16 smb_cmd = le16_to_cpu(midEntry->command); |
| Tainted var | smb_cmd |
| Subscript | [] line 78 |
| Sink snippet | cifs_stats_inc(&server->num_cmds[smb_cmd]); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding 1 — inside the smb_cmd < NUMBER_OF_SMB2_COMMANDS block.
Finding #9 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 45 |
| Taint snippet | __u16 smb_cmd = le16_to_cpu(midEntry->command); |
| Tainted var | smb_cmd |
| Subscript | [] line 79 |
| Sink snippet | server->time_per_cmd[smb_cmd] += roundtrip_time; |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding 1 — inside the smb_cmd < NUMBER_OF_SMB2_COMMANDS block.
Finding #10 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 45 |
| Taint snippet | __u16 smb_cmd = le16_to_cpu(midEntry->command); |
| Tainted var | smb_cmd |
| Subscript | [] line 99 |
| Sink snippet | cifs_stats_inc(&server->smb2slowcmd[smb_cmd]); |
| Possibly guarded | yes (heuristic) |
Dismissed: Guarded by smb_cmd < NUMBER_OF_SMB2_COMMANDS at line 98 before the smb2slowcmd array access.