Contents
net/6lowpan/ (2 functions) — 0 real, 4 FP
net/802/ (1 function) — 0 real, 1 FP
net/atm/ (1 function) — 0 real, 5 FP
net/batman-adv/ (10 functions) — 1 real, 14 FP
batadv_v_ogm_forward() — bat_v_ogm.c FP
batadv_v_ogm_process_per_outif() — bat_v_ogm.c BUG
batadv_frag_insert_packet() — fragmentation.c FP
batadv_frag_merge_packets() — fragmentation.c FP
batadv_recv_mcast_packet() — routing.c FP
batadv_recv_unicast_tvlv() — routing.c FP
batadv_send_other_tt_response() — translation-table.c FP
batadv_tvlv_container_ogm_append() — tvlv.c FP
batadv_tvlv_container_register() — tvlv.c FP
batadv_tvlv_ogm_receive() — tvlv.c FP
net/bluetooth/ (6 functions) — 0 real, 19 FP
net/bluetooth/bnep/ (2 functions) — 0 real, 2 FP
net/bluetooth/ (6 functions) — 0 real, 19 FP
net/bpf/ (5 functions) — 0 real, 6 FP
net/bridge/ (4 functions) — 0 real, 12 FP
net/bridge/netfilter/ (5 functions) — 0 real, 5 FP
net/ceph/ (7 functions) — 3 real, 10 FP
net/ (3 functions) — 0 real, 3 FP
net/core/ (9 functions) — 0 real, 14 FP
net/dsa/ (6 functions) — 1 real, 10 FP
net/ethtool/ (14 functions) — 0 real, 16 FP
ethtool_cmis_cdb_execute_epl_cmd() — cmis_cdb.c FP
ethtool_copy_validate_indir() — ioctl.c FP
ethtool_get_dump_data() — ioctl.c FP
ethtool_get_features() — ioctl.c FP
ethtool_get_phy_stats() — ioctl.c FP
ethtool_get_sset_info() — ioctl.c FP
ethtool_get_stats() — ioctl.c FP
ethtool_get_tunable() — ioctl.c FP
ethtool_rxnfc_copy_from_compat() — ioctl.c FP
ethtool_rxnfc_copy_from_user() — ioctl.c FP
ethtool_rxnfc_copy_to_compat() — ioctl.c FP
ethtool_rxnfc_copy_to_user() — ioctl.c FP
ethtool_self_test() — ioctl.c FP
get_phy_tunable() — ioctl.c FP
net/ieee802154/ (1 function) — 0 real, 1 FP
net/ipv4/ (15 functions) — 0 real, 35 FP
cipso_v4_map_cat_enum_ntoh() — cipso_ipv4.c FP
cipso_v4_map_cat_rng_ntoh() — cipso_ipv4.c FP
rtentry_to_fib_config() — fib_frontend.c FP
fib_table_insert() — fib_trie.c FP
fib_table_lookup() — fib_trie.c FP
icmp_build_probe() — icmp.c FP
igmp_heard_query() — igmp.c FP
tcp_v4_early_demux() — ip_input.c FP
ic_bootp_recv() — ipconfig.c FP
net/ipv4/netfilter/ (4 functions) — 0 real, 4 FP
net/ipv4/ (15 functions) — 0 real, 35 FP
net/ipv6/ (9 functions) — 0 real, 10 FP
net/ipv6/netfilter/ (2 functions) — 0 real, 3 FP
net/ipv6/ (9 functions) — 0 real, 10 FP
net/l2tp/ (3 functions) — 0 real, 3 FP
net/llc/ (2 functions) — 0 real, 4 FP
net/mac80211/ (24 functions) — 0 real, 90 FP
ieee80211_process_addba_request() — agg-rx.c FP
ieee80211_process_addba_resp() — agg-tx.c FP
ieee80211_rx_uhr_link_reconfig_req() — ap.c FP
ieee80211_eht_cap_ie_to_sta_eht_cap() — eht.c FP
ieee80211_process_delba() — ht.c FP
ieee80211_mesh_rx_bcn_presp() — mesh.c FP
mesh_rmc_check() — mesh.c FP
mesh_process_plink_frame() — mesh_plink.c FP
ieee80211_assoc_config_link() — mlme.c FP
ieee80211_max_rx_chains() — mlme.c FP
ieee80211_ml_epcs() — mlme.c FP
ieee80211_ml_reconfiguration() — mlme.c FP
ieee80211_rx_mgmt_assoc_resp() — mlme.c FP
ieee80211_rx_mgmt_beacon() — mlme.c FP
ieee80211_verify_peer_he_mcs_support() — mlme.c FP
ieee80211_verify_sta_he_mcs_support() — mlme.c FP
ieee80211_verify_sta_vht_mcs_support() — mlme.c FP
ieee80211_rx_h_ctrl() — rx.c FP
ieee80211_rx_h_defragment() — rx.c FP
ieee80211_sta_nss_capability() — sta_info.c FP
__ieee80211_tx_status() — status.c FP
tkip_mixing_phase1() — tkip.c FP
tkip_mixing_phase2() — tkip.c FP
ieee80211_apply_vhtcap_overrides() — vht.c FP
net/mctp/ (2 functions) — 0 real, 2 FP
net/mptcp/ (3 functions) — 0 real, 3 FP
net/netfilter/ipset/ (16 functions) — 0 real, 24 FP
ip_set_sockfn_get() — ip_set_core.c FP
hash_ip4_uadt() — ip_set_hash_ip.c FP
hash_ipmark4_uadt() — ip_set_hash_ipmark.c FP
hash_ipport4_uadt() — ip_set_hash_ipport.c FP
hash_ipport6_uadt() — ip_set_hash_ipport.c FP
hash_ipportip4_uadt() — ip_set_hash_ipportip.c FP
hash_ipportip6_uadt() — ip_set_hash_ipportip.c FP
hash_ipportnet4_uadt() — ip_set_hash_ipportnet.c FP
hash_ipportnet6_uadt() — ip_set_hash_ipportnet.c FP
hash_net4_uadt() — ip_set_hash_net.c FP
hash_netiface4_uadt() — ip_set_hash_netiface.c FP
hash_netnet4_uadt() — ip_set_hash_netnet.c FP
hash_netport4_uadt() — ip_set_hash_netport.c FP
hash_netport6_uadt() — ip_set_hash_netport.c FP
hash_netportnet4_uadt() — ip_set_hash_netportnet.c FP
hash_netportnet6_uadt() — ip_set_hash_netportnet.c FP
net/netfilter/ipvs/ (5 functions) — 0 real, 13 FP
net/netfilter/ (13 functions) — 2 real, 60 FP
conntrack_pptp_help() — nf_conntrack_pptp.c FP
hash_by_src() — nf_nat_core.c FP
nf_tables_delchain() — nf_tables_api.c FP
nf_tables_newchain() — nf_tables_api.c FP
nf_tables_newobj() — nf_tables_api.c FP
nf_tables_newrule() — nf_tables_api.c FP
nf_tables_newset() — nf_tables_api.c FP
nfnl_cthelper_parse_expect_policy() — nfnetlink_cthelper.c FP
nfnl_hook_dump_start() — nfnetlink_hook.c FP
nft_cmp_select_ops() — nft_cmp.c BUG
nft_reg_to_type() — nft_immediate.c BUG
xt_data_to_user() — x_tables.c FP
xt_obj_to_user() — x_tables.c FP
net/netlabel/ (2 functions) — 0 real, 2 FP
net/qrtr/ (1 function) — 0 real, 2 FP
net/rds/ (7 functions) — 3 real, 4 FP
net/rfkill/ (1 function) — 0 real, 1 FP
net/rxrpc/ (9 functions) — 0 real, 24 FP
rxrpc_preparse_xdr() — key.c FP
rxrpc_preparse_xdr_rxkad() — key.c FP
rxrpc_preparse_xdr_yfs_rxgk() — key.c FP
rxgk_verify_authenticator() — rxgk.c FP
rxgk_verify_response() — rxgk.c FP
rxgk_yfs_decode_ticket() — rxgk_app.c FP
rxkad_verify_packet() — rxkad.c FP
rxkad_verify_packet_1() — rxkad.c FP
rxkad_verify_packet_2() — rxkad.c FP
net/sctp/ (36 functions) — 22 real, 80 FP
sctp_association_init() — associola.c BUG
__sctp_auth_cid() — auth.c BUG
sctp_auth_asoc_get_hmac() — auth.c MIXED
sctp_auth_asoc_set_default_hmac() — auth.c MIXED
sctp_auth_asoc_verify_hmac_id() — auth.c BUG
sctp_auth_ep_add_chunkid() — auth.c BUG
sctp_auth_make_key_vector() — auth.c BUG
__sctp_rcv_lookup_endpoint() — input.c FP
sctp_acked() — outqueue.c FP
sctp_check_transmitted() — outqueue.c FP
sctp_outq_flush_data() — outqueue.c FP
sctp_outq_sack() — outqueue.c BUG
sctp_sack_update_unack_data() — outqueue.c FP
sctp_get_asconf_response() — sm_make_chunk.c BUG
sctp_pack_cookie() — sm_make_chunk.c FP
sctp_process_asconf() — sm_make_chunk.c BUG
sctp_process_asconf_ack() — sm_make_chunk.c BUG
sctp_process_ext_param() — sm_make_chunk.c BUG
sctp_process_param() — sm_make_chunk.c FP
sctp_verify_ext_param() — sm_make_chunk.c BUG
sctp_verify_param() — sm_make_chunk.c BUG
sctp_eat_data() — sm_statefuns.c FP
sctp_sf_authenticate() — sm_statefuns.c FP
sctp_sf_do_5_1B_init() — sm_statefuns.c FP
sctp_sf_do_unexpected_init() — sm_statefuns.c FP
sctp_get_port_local() — socket.c FP
sctp_getsockopt_hmac_ident() — socket.c FP
sctp_getsockopt_local_addrs() — socket.c FP
sctp_getsockopt_local_auth_chunks() — socket.c BUG
sctp_getsockopt_peer_auth_chunks() — socket.c BUG
sctp_process_strreset_addstrm_in() — stream.c FP
sctp_process_strreset_addstrm_out() — stream.c FP
sctp_process_strreset_inreq() — stream.c FP
sctp_process_strreset_outreq() — stream.c MIXED
sctp_process_strreset_resp() — stream.c FP
sctp_process_strreset_tsnreq() — stream.c FP
net/smc/ (2 functions) — 0 real, 6 FP
net/ (3 functions) — 0 real, 3 FP
net/sunrpc/ (1 function) — 0 real, 1 FP
net/tipc/ (2 functions) — 0 real, 2 FP
net/vmw_vsock/ (1 function) — 0 real, 14 FP
net/wireless/ (8 functions) — 34 real, 57 FP
net/xfrm/ (9 functions) — 2 real, 9 FP
xfrm_input() — xfrm_input.c FP
xfrmi4_err() — xfrm_interface_core.c FP
xfrmi6_err() — xfrm_interface_core.c FP
__input_process_payload() — xfrm_iptfs.c FP
iptfs_input_ordered() — xfrm_iptfs.c FP
xfrm_replay_advance_bmp() — xfrm_replay.c MIXED
xfrm_replay_advance_esn() — xfrm_replay.c MIXED
xfrm_replay_check_bmp() — xfrm_replay.c FP
xfrm_replay_check_esn() — xfrm_replay.c FP
Summary
| Function | File | Assessment | Confidence | Real | FP | Unanalyzed |
|---|---|---|---|---|---|---|
| net/6lowpan/ — 2 functions, 0 real, 4 FP | ||||||
| lowpan_ctx_pfx_write() | net/6lowpan/debugfs.c | FP | high | 0 | 1 | 0 |
| udp_compress() | net/6lowpan/nhc_udp.c | FP | high | 0 | 3 | 0 |
| net/802/ — 1 function, 0 real, 1 FP | ||||||
| mrp_pdu_parse_vecattr() | net/802/mrp.c | FP | high | 0 | 1 | 0 |
| net/atm/ — 1 function, 0 real, 5 FP | ||||||
| atm_dev_ioctl() | net/atm/resources.c | FP | high | 0 | 5 | 0 |
| net/batman-adv/ — 10 functions, 1 real, 14 FP | ||||||
| batadv_v_ogm_forward() | net/batman-adv/bat_v_ogm.c | FP | high | 0 | 4 | 0 |
| batadv_v_ogm_process_per_outif() | net/batman-adv/bat_v_ogm.c | BUG | medium | 1 | 0 | 0 |
| batadv_frag_insert_packet() | net/batman-adv/fragmentation.c | FP | high | 0 | 2 | 0 |
| batadv_frag_merge_packets() | net/batman-adv/fragmentation.c | FP | medium | 0 | 2 | 0 |
| batadv_recv_mcast_packet() | net/batman-adv/routing.c | FP | high | 0 | 1 | 0 |
| batadv_recv_unicast_tvlv() | net/batman-adv/routing.c | FP | high | 0 | 1 | 0 |
| batadv_send_other_tt_response() | net/batman-adv/translation-table.c | FP | high | 0 | 1 | 0 |
| batadv_tvlv_container_ogm_append() | net/batman-adv/tvlv.c | FP | high | 0 | 1 | 0 |
| batadv_tvlv_container_register() | net/batman-adv/tvlv.c | FP | high | 0 | 1 | 0 |
| batadv_tvlv_ogm_receive() | net/batman-adv/tvlv.c | FP | medium | 0 | 1 | 0 |
| net/bluetooth/ — 6 functions, 0 real, 19 FP | ||||||
| lowpan_control_write() | net/bluetooth/6lowpan.c | FP | high | 0 | 1 | 0 |
| net/bluetooth/bnep/ — 2 functions, 0 real, 2 FP | ||||||
| bnep_ctrl_set_mcfilter() | net/bluetooth/bnep/core.c | FP | high | 0 | 1 | 0 |
| bnep_ctrl_set_netfilter() | net/bluetooth/bnep/core.c | FP | high | 0 | 1 | 0 |
| net/bluetooth/ — 6 functions, 0 real, 19 FP | ||||||
| hci_get_conn_list() | net/bluetooth/hci_conn.c | FP | high | 0 | 1 | 0 |
| force_no_mitm_write() | net/bluetooth/hci_debugfs.c | FP | high | 0 | 1 | 0 |
| hci_le_per_adv_report_evt() | net/bluetooth/hci_event.c | FP | high | 0 | 5 | 0 |
| hci_sock_sendmsg() | net/bluetooth/hci_sock.c | FP | high | 0 | 1 | 0 |
| send_hci_cmd_sync() | net/bluetooth/mgmt.c | FP | high | 0 | 10 | 0 |
| net/bpf/ — 5 functions, 0 real, 6 FP | ||||||
| bpf_ctx_finish() | net/bpf/test_run.c | FP | high | 0 | 1 | 0 |
| bpf_ctx_init() | net/bpf/test_run.c | FP | high | 0 | 1 | 0 |
| bpf_prog_test_run_skb() | net/bpf/test_run.c | FP | high | 0 | 1 | 0 |
| bpf_prog_test_run_xdp() | net/bpf/test_run.c | FP | high | 0 | 1 | 0 |
| bpf_test_finish() | net/bpf/test_run.c | FP | high | 0 | 2 | 0 |
| net/bridge/ — 4 functions, 0 real, 12 FP | ||||||
| br_ioctl_stub() | net/bridge/br_ioctl.c | FP | high | 0 | 1 | 0 |
| old_deviceless() | net/bridge/br_ioctl.c | FP | high | 0 | 1 | 0 |
| br_ip4_multicast_igmp3_report() | net/bridge/br_multicast.c | FP | high | 0 | 8 | 0 |
| br_ip6_multicast_mld2_report() | net/bridge/br_multicast.c | FP | high | 0 | 2 | 0 |
| net/bridge/netfilter/ — 5 functions, 0 real, 5 FP | ||||||
| ebt_vlan_mt() | net/bridge/netfilter/ebt_vlan.c | FP | high | 0 | 1 | 0 |
| compat_do_replace() | net/bridge/netfilter/ebtables.c | FP | high | 0 | 1 | 0 |
| compat_match_to_user() | net/bridge/netfilter/ebtables.c | FP | high | 0 | 1 | 0 |
| compat_target_to_user() | net/bridge/netfilter/ebtables.c | FP | high | 0 | 1 | 0 |
| ebt_obj_to_user() | net/bridge/netfilter/ebtables.c | FP | high | 0 | 1 | 0 |
| net/ceph/ — 7 functions, 3 real, 10 FP | ||||||
| encrypt_authorizer() | net/ceph/auth_x.c | FP | high | 0 | 6 | 0 |
| ceph_alloc_middle() | net/ceph/messenger.c | VALIDATE | medium | 1 | 0 | 0 |
| alloc_msg_with_page_vector() | net/ceph/osd_client.c | BUG | high | 2 | 0 | 0 |
| get_reply() | net/ceph/osd_client.c | FP | high | 0 | 1 | 0 |
| handle_reply() | net/ceph/osd_client.c | FP | high | 0 | 1 | 0 |
| osd_sparse_read() | net/ceph/osd_client.c | FP | high | 0 | 1 | 0 |
| prep_next_sparse_read() | net/ceph/osd_client.c | FP | high | 0 | 1 | 0 |
| net/ — 3 functions, 0 real, 3 FP | ||||||
| cmsghdr_from_user_compat_to_kern() | net/compat.c | FP | high | 0 | 1 | 0 |
| put_cmsg_compat() | net/compat.c | FP | high | 0 | 1 | 0 |
| net/core/ — 9 functions, 0 real, 14 FP | ||||||
| netdev_cmd_to_name() | net/core/dev.c | FP | high | 0 | 1 | 0 |
| ptype_head() | net/core/dev.c | FP | high | 0 | 1 | 0 |
| __get_filter() | net/core/filter.c | FP | high | 0 | 1 | 0 |
| bpf_prog_create_from_user() | net/core/filter.c | FP | high | 0 | 1 | 0 |
| ptype_seq_next() | net/core/net-procfs.c | FP | high | 0 | 2 | 0 |
| pf() | net/core/pktgen.c | FP | high | 0 | 4 | 0 |
| skb_checksum_setup_ipv6() | net/core/skbuff.c | FP | high | 0 | 1 | 0 |
| skb_mpls_dec_ttl() | net/core/skbuff.c | FP | high | 0 | 1 | 0 |
| sock_ioctl_inout() | net/core/sock.c | FP | high | 0 | 2 | 0 |
| net/dsa/ — 6 functions, 1 real, 10 FP | ||||||
| ar9331_tag_rcv() | net/dsa/tag_ar9331.c | FP | high | 0 | 2 | 0 |
| ksz_xmit_timestamp() | net/dsa/tag_ksz.c | FP | high | 0 | 1 | 0 |
| qca_tag_rcv() | net/dsa/tag_qca.c | FP | high | 0 | 2 | 0 |
| rtl4a_tag_rcv() | net/dsa/tag_rtl4_a.c | FP | high | 0 | 2 | 0 |
| rtl8_4_read_tag() | net/dsa/tag_rtl8_4.c | FP | high | 0 | 3 | 0 |
| sja1110_rcv_inband_control_extension() | net/dsa/tag_sja1105.c | BUG | medium | 1 | 0 | 0 |
| net/ethtool/ — 14 functions, 0 real, 16 FP | ||||||
| ethtool_cmis_cdb_execute_epl_cmd() | net/ethtool/cmis_cdb.c | FP | medium | 0 | 2 | 0 |
| ethtool_copy_validate_indir() | net/ethtool/ioctl.c | FP | high | 0 | 1 | 0 |
| ethtool_get_dump_data() | net/ethtool/ioctl.c | FP | high | 0 | 1 | 0 |
| ethtool_get_features() | net/ethtool/ioctl.c | FP | high | 0 | 1 | 0 |
| ethtool_get_phy_stats() | net/ethtool/ioctl.c | FP | medium | 0 | 1 | 0 |
| ethtool_get_sset_info() | net/ethtool/ioctl.c | FP | high | 0 | 1 | 0 |
| ethtool_get_stats() | net/ethtool/ioctl.c | FP | high | 0 | 1 | 0 |
| ethtool_get_tunable() | net/ethtool/ioctl.c | FP | high | 0 | 1 | 0 |
| ethtool_rxnfc_copy_from_compat() | net/ethtool/ioctl.c | FP | high | 0 | 1 | 0 |
| ethtool_rxnfc_copy_from_user() | net/ethtool/ioctl.c | FP | high | 0 | 1 | 0 |
| ethtool_rxnfc_copy_to_compat() | net/ethtool/ioctl.c | FP | high | 0 | 1 | 0 |
| ethtool_rxnfc_copy_to_user() | net/ethtool/ioctl.c | FP | high | 0 | 2 | 0 |
| ethtool_self_test() | net/ethtool/ioctl.c | FP | high | 0 | 1 | 0 |
| get_phy_tunable() | net/ethtool/ioctl.c | FP | high | 0 | 1 | 0 |
| net/ieee802154/ — 1 function, 0 real, 1 FP | ||||||
| dgram_getsockopt() | net/ieee802154/socket.c | FP | high | 0 | 1 | 0 |
| net/ipv4/ — 15 functions, 0 real, 35 FP | ||||||
| cipso_v4_map_cat_enum_ntoh() | net/ipv4/cipso_ipv4.c | FP | high | 0 | 1 | 0 |
| cipso_v4_map_cat_rng_ntoh() | net/ipv4/cipso_ipv4.c | FP | high | 0 | 2 | 0 |
| rtentry_to_fib_config() | net/ipv4/fib_frontend.c | FP | high | 0 | 1 | 0 |
| fib_table_insert() | net/ipv4/fib_trie.c | FP | high | 0 | 1 | 0 |
| fib_table_lookup() | net/ipv4/fib_trie.c | FP | high | 0 | 1 | 0 |
| icmp_build_probe() | net/ipv4/icmp.c | FP | high | 0 | 18 | 0 |
| igmp_heard_query() | net/ipv4/igmp.c | FP | high | 0 | 1 | 0 |
| tcp_v4_early_demux() | net/ipv4/ip_input.c | FP | high | 0 | 1 | 0 |
| ic_bootp_recv() | net/ipv4/ipconfig.c | FP | medium | 0 | 2 | 0 |
| net/ipv4/netfilter/ — 4 functions, 0 real, 4 FP | ||||||
| __do_replace() | net/ipv4/netfilter/arp_tables.c | FP | high | 0 | 1 | 0 |
| __do_replace() | net/ipv4/netfilter/ip_tables.c | FP | high | 0 | 1 | 0 |
| pptp_inbound_pkt() | net/ipv4/netfilter/nf_nat_pptp.c | FP | high | 0 | 1 | 0 |
| pptp_outbound_pkt() | net/ipv4/netfilter/nf_nat_pptp.c | FP | high | 0 | 1 | 0 |
| net/ipv4/ — 15 functions, 0 real, 35 FP | ||||||
| __cookie_v4_check() | net/ipv4/syncookies.c | FP | high | 0 | 1 | 0 |
| tcp_v4_err() | net/ipv4/tcp_ipv4.c | FP | high | 0 | 1 | 0 |
| tcp4_check_fraglist_gro() | net/ipv4/tcp_offload.c | FP | high | 0 | 1 | 0 |
| __udp4_lib_lookup() | net/ipv4/udp.c | FP | high | 0 | 2 | 0 |
| __udp4_lib_mcast_deliver() | net/ipv4/udp.c | FP | high | 0 | 1 | 0 |
| __udp4_lib_mcast_demux_lookup() | net/ipv4/udp.c | FP | high | 0 | 1 | 0 |
| net/ipv6/ — 9 functions, 0 real, 10 FP | ||||||
| esp6_find_tcp_sk() | net/ipv6/esp6.c | FP | high | 0 | 1 | 0 |
| tcp_v6_early_demux() | net/ipv6/ip6_input.c | FP | high | 0 | 1 | 0 |
| net/ipv6/netfilter/ — 2 functions, 0 real, 3 FP | ||||||
| __do_replace() | net/ipv6/netfilter/ip6_tables.c | FP | high | 0 | 1 | 0 |
| nf_tproxy_get_sock_v6() | net/ipv6/netfilter/nf_tproxy_ipv6.c | FP | high | 0 | 2 | 0 |
| net/ipv6/ — 9 functions, 0 real, 10 FP | ||||||
| do_rawv6_getsockopt() | net/ipv6/raw.c | FP | high | 0 | 1 | 0 |
| ipip6_tunnel_get_prl() | net/ipv6/sit.c | FP | high | 0 | 1 | 0 |
| __cookie_v6_check() | net/ipv6/syncookies.c | FP | high | 0 | 1 | 0 |
| tcp_v6_err() | net/ipv6/tcp_ipv6.c | FP | high | 0 | 1 | 0 |
| tcp6_check_fraglist_gro() | net/ipv6/tcpv6_offload.c | FP | high | 0 | 1 | 0 |
| __udp6_lib_lookup() | net/ipv6/udp.c | FP | high | 0 | 2 | 0 |
| __udp6_lib_mcast_deliver() | net/ipv6/udp.c | FP | high | 0 | 1 | 0 |
| net/l2tp/ — 3 functions, 0 real, 3 FP | ||||||
| l2tp_udp_encap_recv() | net/l2tp/l2tp_core.c | FP | high | 0 | 1 | 0 |
| l2tp_ip_recv() | net/l2tp/l2tp_ip.c | FP | high | 0 | 1 | 0 |
| l2tp_ip6_recv() | net/l2tp/l2tp_ip6.c | FP | high | 0 | 1 | 0 |
| net/llc/ — 2 functions, 0 real, 4 FP | ||||||
| llc_sap_action_send_test_r() | net/llc/llc_s_ac.c | FP | high | 0 | 2 | 0 |
| llc_station_ac_send_test_r() | net/llc/llc_station.c | FP | high | 0 | 2 | 0 |
| net/mac80211/ — 24 functions, 0 real, 90 FP | ||||||
| ieee80211_process_addba_request() | net/mac80211/agg-rx.c | FP | high | 0 | 5 | 0 |
| ieee80211_process_addba_resp() | net/mac80211/agg-tx.c | FP | high | 0 | 4 | 0 |
| ieee80211_rx_uhr_link_reconfig_req() | net/mac80211/ap.c | FP | high | 0 | 3 | 0 |
| ieee80211_eht_cap_ie_to_sta_eht_cap() | net/mac80211/eht.c | FP | medium | 0 | 2 | 0 |
| ieee80211_process_delba() | net/mac80211/ht.c | FP | high | 0 | 4 | 0 |
| ieee80211_mesh_rx_bcn_presp() | net/mac80211/mesh.c | FP | high | 0 | 8 | 0 |
| mesh_rmc_check() | net/mac80211/mesh.c | FP | high | 0 | 3 | 0 |
| mesh_process_plink_frame() | net/mac80211/mesh_plink.c | FP | high | 0 | 1 | 0 |
| ieee80211_assoc_config_link() | net/mac80211/mlme.c | FP | high | 0 | 5 | 0 |
| ieee80211_max_rx_chains() | net/mac80211/mlme.c | FP | high | 0 | 2 | 0 |
| ieee80211_ml_epcs() | net/mac80211/mlme.c | FP | high | 0 | 5 | 0 |
| ieee80211_ml_reconfiguration() | net/mac80211/mlme.c | FP | high | 0 | 5 | 0 |
| ieee80211_rx_mgmt_assoc_resp() | net/mac80211/mlme.c | FP | high | 0 | 1 | 0 |
| ieee80211_rx_mgmt_beacon() | net/mac80211/mlme.c | FP | high | 0 | 9 | 0 |
| ieee80211_verify_peer_he_mcs_support() | net/mac80211/mlme.c | FP | high | 0 | 3 | 0 |
| ieee80211_verify_sta_he_mcs_support() | net/mac80211/mlme.c | FP | high | 0 | 3 | 0 |
| ieee80211_verify_sta_vht_mcs_support() | net/mac80211/mlme.c | FP | high | 0 | 3 | 0 |
| ieee80211_rx_h_ctrl() | net/mac80211/rx.c | FP | high | 0 | 1 | 0 |
| ieee80211_rx_h_defragment() | net/mac80211/rx.c | FP | high | 0 | 2 | 0 |
| ieee80211_sta_nss_capability() | net/mac80211/sta_info.c | FP | high | 0 | 3 | 0 |
| __ieee80211_tx_status() | net/mac80211/status.c | FP | high | 0 | 3 | 0 |
| tkip_mixing_phase1() | net/mac80211/tkip.c | FP | high | 0 | 5 | 0 |
| tkip_mixing_phase2() | net/mac80211/tkip.c | FP | high | 0 | 6 | 0 |
| ieee80211_apply_vhtcap_overrides() | net/mac80211/vht.c | FP | high | 0 | 4 | 0 |
| net/mctp/ — 2 functions, 0 real, 2 FP | ||||||
| mctp_ioctl_tag_copy_from_user() | net/mctp/af_mctp.c | FP | high | 0 | 1 | 0 |
| mctp_ioctl_tag_copy_to_user() | net/mctp/af_mctp.c | FP | high | 0 | 1 | 0 |
| net/mptcp/ — 3 functions, 0 real, 3 FP | ||||||
| mptcp_get_subflow_data() | net/mptcp/sockopt.c | FP | high | 0 | 1 | 0 |
| mptcp_put_full_info() | net/mptcp/sockopt.c | FP | high | 0 | 1 | 0 |
| mptcp_put_subflow_data() | net/mptcp/sockopt.c | FP | high | 0 | 1 | 0 |
| net/netfilter/ipset/ — 16 functions, 0 real, 24 FP | ||||||
| ip_set_sockfn_get() | net/netfilter/ipset/ip_set_core.c | FP | high | 0 | 1 | 0 |
| hash_ip4_uadt() | net/netfilter/ipset/ip_set_hash_ip.c | FP | high | 0 | 1 | 0 |
| hash_ipmark4_uadt() | net/netfilter/ipset/ip_set_hash_ipmark.c | FP | high | 0 | 1 | 0 |
| hash_ipport4_uadt() | net/netfilter/ipset/ip_set_hash_ipport.c | FP | high | 0 | 2 | 0 |
| hash_ipport6_uadt() | net/netfilter/ipset/ip_set_hash_ipport.c | FP | high | 0 | 1 | 0 |
| hash_ipportip4_uadt() | net/netfilter/ipset/ip_set_hash_ipportip.c | FP | high | 0 | 2 | 0 |
| hash_ipportip6_uadt() | net/netfilter/ipset/ip_set_hash_ipportip.c | FP | high | 0 | 1 | 0 |
| hash_ipportnet4_uadt() | net/netfilter/ipset/ip_set_hash_ipportnet.c | FP | high | 0 | 3 | 0 |
| hash_ipportnet6_uadt() | net/netfilter/ipset/ip_set_hash_ipportnet.c | FP | high | 0 | 1 | 0 |
| hash_net4_uadt() | net/netfilter/ipset/ip_set_hash_net.c | FP | high | 0 | 1 | 0 |
| hash_netiface4_uadt() | net/netfilter/ipset/ip_set_hash_netiface.c | FP | high | 0 | 1 | 0 |
| hash_netnet4_uadt() | net/netfilter/ipset/ip_set_hash_netnet.c | FP | high | 0 | 2 | 0 |
| hash_netport4_uadt() | net/netfilter/ipset/ip_set_hash_netport.c | FP | high | 0 | 2 | 0 |
| hash_netport6_uadt() | net/netfilter/ipset/ip_set_hash_netport.c | FP | high | 0 | 1 | 0 |
| hash_netportnet4_uadt() | net/netfilter/ipset/ip_set_hash_netportnet.c | FP | high | 0 | 3 | 0 |
| hash_netportnet6_uadt() | net/netfilter/ipset/ip_set_hash_netportnet.c | FP | high | 0 | 1 | 0 |
| net/netfilter/ipvs/ — 5 functions, 0 real, 13 FP | ||||||
| do_ip_vs_get_ctl() | net/netfilter/ipvs/ip_vs_ctl.c | FP | high | 0 | 1 | 0 |
| set_sctp_state() | net/netfilter/ipvs/ip_vs_proto_sctp.c | FP | high | 0 | 9 | 0 |
| ip_vs_proc_sync_conn() | net/netfilter/ipvs/ip_vs_sync.c | FP | high | 0 | 1 | 0 |
| ip_vs_process_message() | net/netfilter/ipvs/ip_vs_sync.c | FP | high | 0 | 1 | 0 |
| ip_vs_process_message_v0() | net/netfilter/ipvs/ip_vs_sync.c | FP | high | 0 | 1 | 0 |
| net/netfilter/ — 13 functions, 2 real, 60 FP | ||||||
| conntrack_pptp_help() | net/netfilter/nf_conntrack_pptp.c | FP | high | 0 | 1 | 0 |
| hash_by_src() | net/netfilter/nf_nat_core.c | FP | high | 0 | 1 | 0 |
| nf_tables_delchain() | net/netfilter/nf_tables_api.c | FP | high | 0 | 1 | 0 |
| nf_tables_newchain() | net/netfilter/nf_tables_api.c | FP | high | 0 | 2 | 0 |
| nf_tables_newobj() | net/netfilter/nf_tables_api.c | FP | high | 0 | 3 | 0 |
| nf_tables_newrule() | net/netfilter/nf_tables_api.c | FP | high | 0 | 14 | 0 |
| nf_tables_newset() | net/netfilter/nf_tables_api.c | FP | high | 0 | 31 | 0 |
| nfnl_cthelper_parse_expect_policy() | net/netfilter/nfnetlink_cthelper.c | FP | high | 0 | 1 | 0 |
| nfnl_hook_dump_start() | net/netfilter/nfnetlink_hook.c | FP | high | 0 | 4 | 0 |
| nft_cmp_select_ops() | net/netfilter/nft_cmp.c | BUG | medium | 1 | 0 | 0 |
| nft_reg_to_type() | net/netfilter/nft_immediate.c | BUG | medium | 1 | 0 | 0 |
| xt_data_to_user() | net/netfilter/x_tables.c | FP | high | 0 | 1 | 0 |
| xt_obj_to_user() | net/netfilter/x_tables.c | FP | high | 0 | 1 | 0 |
| net/netlabel/ — 2 functions, 0 real, 2 FP | ||||||
| netlbl_af4list_audit_addr() | net/netlabel/netlabel_addrlist.c | FP | high | 0 | 1 | 0 |
| netlbl_af6list_audit_addr() | net/netlabel/netlabel_addrlist.c | FP | high | 0 | 1 | 0 |
| net/qrtr/ — 1 function, 0 real, 2 FP | ||||||
| qrtr_ns_worker() | net/qrtr/ns.c | MIXED | high | 0 | 2 | 0 |
| net/rds/ — 7 functions, 3 real, 4 FP | ||||||
| rds_add_bound() | net/rds/bind.c | FP | high | 0 | 1 | 0 |
| rds_cong_clear_bit() | net/rds/cong.c | BUG | high | 0 | 1 | 0 |
| rds_cong_set_bit() | net/rds/cong.c | BUG | high | 1 | 0 | 0 |
| rds_cong_test_bit() | net/rds/cong.c | FP | high | 0 | 1 | 0 |
| rds_ib_inc_copy_to_user() | net/rds/ib_recv.c | BUG | medium | 1 | 0 | 0 |
| rds_ib_xmit() | net/rds/ib_send.c | FP | high | 0 | 1 | 0 |
| rds_message_inc_copy_to_user() | net/rds/message.c | BUG | medium | 1 | 0 | 0 |
| net/rfkill/ — 1 function, 0 real, 1 FP | ||||||
| rfkill_fop_read() | net/rfkill/core.c | FP | high | 0 | 1 | 0 |
| net/rxrpc/ — 9 functions, 0 real, 24 FP | ||||||
| rxrpc_preparse_xdr() | net/rxrpc/key.c | FP | high | 0 | 9 | 0 |
| rxrpc_preparse_xdr_rxkad() | net/rxrpc/key.c | FP | high | 0 | 2 | 0 |
| rxrpc_preparse_xdr_yfs_rxgk() | net/rxrpc/key.c | FP | high | 0 | 3 | 0 |
| rxgk_verify_authenticator() | net/rxrpc/rxgk.c | FP | high | 0 | 1 | 0 |
| rxgk_verify_response() | net/rxrpc/rxgk.c | FP | high | 0 | 1 | 0 |
| rxgk_yfs_decode_ticket() | net/rxrpc/rxgk_app.c | FP | high | 0 | 5 | 0 |
| rxkad_verify_packet() | net/rxrpc/rxkad.c | FP | high | 0 | 1 | 0 |
| rxkad_verify_packet_1() | net/rxrpc/rxkad.c | FP | high | 0 | 1 | 0 |
| rxkad_verify_packet_2() | net/rxrpc/rxkad.c | FP | high | 0 | 1 | 0 |
| net/sctp/ — 36 functions, 22 real, 80 FP | ||||||
| sctp_association_init() | net/sctp/associola.c | BUG | high | 2 | 0 | 0 |
| __sctp_auth_cid() | net/sctp/auth.c | BUG | high | 1 | 0 | 0 |
| sctp_auth_asoc_get_hmac() | net/sctp/auth.c | MIXED | high | 1 | 2 | 0 |
| sctp_auth_asoc_set_default_hmac() | net/sctp/auth.c | MIXED | high | 1 | 1 | 0 |
| sctp_auth_asoc_verify_hmac_id() | net/sctp/auth.c | BUG | medium | 1 | 0 | 0 |
| sctp_auth_ep_add_chunkid() | net/sctp/auth.c | BUG | high | 1 | 0 | 0 |
| sctp_auth_make_key_vector() | net/sctp/auth.c | BUG | high | 3 | 9 | 0 |
| __sctp_rcv_lookup_endpoint() | net/sctp/input.c | FP | high | 0 | 1 | 0 |
| sctp_acked() | net/sctp/outqueue.c | FP | medium | 0 | 2 | 0 |
| sctp_check_transmitted() | net/sctp/outqueue.c | FP | high | 0 | 1 | 0 |
| sctp_outq_flush_data() | net/sctp/outqueue.c | FP | high | 0 | 1 | 0 |
| sctp_outq_sack() | net/sctp/outqueue.c | BUG | high | 1 | 0 | 0 |
| sctp_sack_update_unack_data() | net/sctp/outqueue.c | FP | medium | 0 | 1 | 0 |
| sctp_get_asconf_response() | net/sctp/sm_make_chunk.c | BUG | high | 1 | 0 | 0 |
| sctp_pack_cookie() | net/sctp/sm_make_chunk.c | FP | medium | 0 | 1 | 0 |
| sctp_process_asconf() | net/sctp/sm_make_chunk.c | BUG | medium | 1 | 0 | 0 |
| sctp_process_asconf_ack() | net/sctp/sm_make_chunk.c | BUG | medium | 4 | 1 | 0 |
| sctp_process_ext_param() | net/sctp/sm_make_chunk.c | BUG | high | 1 | 0 | 0 |
| sctp_process_param() | net/sctp/sm_make_chunk.c | FP | high | 0 | 1 | 0 |
| sctp_verify_ext_param() | net/sctp/sm_make_chunk.c | BUG | high | 1 | 0 | 0 |
| sctp_verify_param() | net/sctp/sm_make_chunk.c | BUG | high | 1 | 0 | 0 |
| sctp_eat_data() | net/sctp/sm_statefuns.c | FP | high | 0 | 1 | 0 |
| sctp_sf_authenticate() | net/sctp/sm_statefuns.c | FP | medium | 0 | 2 | 0 |
| sctp_sf_do_5_1B_init() | net/sctp/sm_statefuns.c | FP | high | 0 | 4 | 0 |
| sctp_sf_do_unexpected_init() | net/sctp/sm_statefuns.c | FP | high | 0 | 4 | 0 |
| sctp_get_port_local() | net/sctp/socket.c | FP | high | 0 | 1 | 0 |
| sctp_getsockopt_hmac_ident() | net/sctp/socket.c | FP | medium | 0 | 1 | 0 |
| sctp_getsockopt_local_addrs() | net/sctp/socket.c | FP | high | 0 | 1 | 0 |
| sctp_getsockopt_local_auth_chunks() | net/sctp/socket.c | BUG | high | 1 | 0 | 0 |
| sctp_getsockopt_peer_auth_chunks() | net/sctp/socket.c | BUG | medium | 1 | 0 | 0 |
| sctp_process_strreset_addstrm_in() | net/sctp/stream.c | FP | high | 0 | 2 | 0 |
| sctp_process_strreset_addstrm_out() | net/sctp/stream.c | FP | high | 0 | 2 | 0 |
| sctp_process_strreset_inreq() | net/sctp/stream.c | FP | high | 0 | 10 | 0 |
| sctp_process_strreset_outreq() | net/sctp/stream.c | MIXED | medium | 0 | 15 | 0 |
| sctp_process_strreset_resp() | net/sctp/stream.c | FP | high | 0 | 9 | 0 |
| sctp_process_strreset_tsnreq() | net/sctp/stream.c | FP | high | 0 | 7 | 0 |
| net/smc/ — 2 functions, 0 real, 6 FP | ||||||
| smc_clc_msg_hdr_valid() | net/smc/smc_clc.c | FP | medium | 0 | 4 | 0 |
| smc_rtoken_set() | net/smc/smc_core.c | FP | high | 0 | 2 | 0 |
| net/ — 3 functions, 0 real, 3 FP | ||||||
| put_user_ifreq() | net/socket.c | FP | high | 0 | 1 | 0 |
| net/sunrpc/ — 1 function, 0 real, 1 FP | ||||||
| rpc_pipe_generic_upcall() | net/sunrpc/rpc_pipe.c | FP | high | 0 | 1 | 0 |
| net/tipc/ — 2 functions, 0 real, 2 FP | ||||||
| tipc_nl_compat_name_table_dump_header() | net/tipc/netlink_compat.c | FP | high | 0 | 1 | 0 |
| tipc_rcv() | net/tipc/node.c | FP | high | 0 | 1 | 0 |
| net/vmw_vsock/ — 1 function, 0 real, 14 FP | ||||||
| virtio_transport_build_skb() | net/vmw_vsock/virtio_transport_common.c | FP | high | 0 | 14 | 0 |
| net/wireless/ — 8 functions, 34 real, 57 FP | ||||||
| nl80211_send_iftype_data() | net/wireless/nl80211.c | FP | high | 0 | 1 | 0 |
| __regdb_query_wmm() | net/wireless/reg.c | BUG | high | 6 | 8 | 0 |
| regdb_query_country() | net/wireless/reg.c | MIXED | medium | 20 | 32 | 0 |
| set_wmm_rule() | net/wireless/reg.c | BUG | medium | 8 | 0 | 0 |
| valid_country() | net/wireless/reg.c | FP | high | 0 | 10 | 0 |
| cfg80211_parse_ml_elem_sta_data() | net/wireless/scan.c | FP | high | 0 | 3 | 0 |
| cfg80211_get_p2p_attr() | net/wireless/util.c | FP | high | 0 | 1 | 0 |
| ioctl_private_iw_point() | net/wireless/wext-priv.c | FP | high | 0 | 2 | 0 |
| net/xfrm/ — 9 functions, 2 real, 9 FP | ||||||
| xfrm_input() | net/xfrm/xfrm_input.c | FP | high | 0 | 1 | 0 |
| xfrmi4_err() | net/xfrm/xfrm_interface_core.c | FP | high | 0 | 1 | 0 |
| xfrmi6_err() | net/xfrm/xfrm_interface_core.c | FP | high | 0 | 1 | 0 |
| __input_process_payload() | net/xfrm/xfrm_iptfs.c | FP | high | 0 | 1 | 0 |
| iptfs_input_ordered() | net/xfrm/xfrm_iptfs.c | FP | high | 0 | 1 | 0 |
| xfrm_replay_advance_bmp() | net/xfrm/xfrm_replay.c | MIXED | medium | 1 | 1 | 0 |
| xfrm_replay_advance_esn() | net/xfrm/xfrm_replay.c | MIXED | high | 1 | 1 | 0 |
| xfrm_replay_check_bmp() | net/xfrm/xfrm_replay.c | FP | high | 0 | 1 | 0 |
| xfrm_replay_check_esn() | net/xfrm/xfrm_replay.c | FP | high | 0 | 1 | 0 |
Function Details
lowpan_ctx_pfx_write() — net/6lowpan/debugfs.c FP confidence=high
The flagged 'count' parameter is the standard VFS write syscall size argument, which is user-controlled in the sense that it comes from the write(2) syscall. However, the copy_from_user() call uses min_t(size_t, sizeof(buf) - 1, count) as the size argument, which clamps the copy length to at most sizeof(buf)-1 = 127 bytes regardless of the user-supplied count. The destination buffer 'buf' is a 128-byte stack array, so the copy is always safe. The scanner incorrectly treats the result of min_t() as tainted because 'count' is one of its inputs, but min_t guarantees the result never exceeds sizeof(buf)-1. No counterexample can be constructed that would cause an OOB write because min_t's upper bound is sizeof(buf)-1=127 < sizeof(buf)=128.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_from_user() line 135 |
| Taint snippet | if (copy_from_user(&buf, user_buf, min_t(size_t, sizeof(buf) - 1, |
| Tainted var | min_t(size_t, sizeof(buf) - 1,
count) |
| Unvalidated size | copy_from_user() arg 2 line 135 — size min_t(size_t, sizeof(buf) - 1,
count) |
| Sink snippet | if (copy_from_user(&buf, user_buf, min_t(size_t, sizeof(buf) - 1, |
| Possibly guarded | no |
Dismissed: The size passed to copy_from_user is min_t(size_t, sizeof(buf) - 1, count), which evaluates to at most 127 regardless of the user-supplied 'count'. The destination buffer is 128 bytes. No counterexample exists where this causes an OOB write: for any value of count, the copy is bounded to 127 bytes into a 128-byte buffer. This is a textbook correct pattern for debugfs write handlers. The scanner false-positives because it tracks 'count' as tainted through min_t without modeling min_t's bounding semantics.
udp_compress() — net/6lowpan/nhc_udp.c FP confidence=high
All three truncations are guarded by preceding conditional checks that constrain the subtracted value to fit within u8. For finding #1, the 4-bit mask check ensures dest and source offsets are each in [0,15], so the combined byte is at most 255. For findings #2 and #3, the 8-bit mask check ensures the port offset is in [0,255], exactly fitting u8. The scanner did not account for the mathematical postconditions established by the mask comparisons.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohs() line 129 |
| Taint snippet | tmp = ntohs(uh->dest) - LOWPAN_NHC_UDP_4BIT_PORT + |
| Tainted var | tmp |
| Truncation | line 129: 16 → 8-bit u8 |
| Sink snippet | tmp = ntohs(uh->dest) - LOWPAN_NHC_UDP_4BIT_PORT + |
| Possibly guarded | no |
Dismissed: The guard `(ntohs(uh->dest) & 0xFFF0) == 0xF0B0` constrains dest to [0xF0B0, 0xF0BF], making dest-LOWPAN_NHC_UDP_4BIT_PORT in [0,15]. Similarly source offset is in [0,15]. Combined: tmp in [0,255] — no counterexample exists that passes the guard and overflows u8.
Finding #2 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohs() line 141 |
| Taint snippet | tmp = ntohs(uh->dest) - LOWPAN_NHC_UDP_8BIT_PORT; |
| Tainted var | tmp |
| Truncation | line 141: 16 → 8-bit u8 |
| Sink snippet | tmp = ntohs(uh->dest) - LOWPAN_NHC_UDP_8BIT_PORT; |
| Possibly guarded | no |
Dismissed: The guard `(ntohs(uh->dest) & 0xFF00) == 0xF000` constrains dest to [0xF000, 0xF0FF], making the subtraction result in [0,255] — exactly the u8 range. No counterexample can pass the guard and overflow.
Finding #3 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohs() line 150 |
| Taint snippet | tmp = ntohs(uh->source) - LOWPAN_NHC_UDP_8BIT_PORT; |
| Tainted var | tmp |
| Truncation | line 150: 16 → 8-bit u8 |
| Sink snippet | tmp = ntohs(uh->source) - LOWPAN_NHC_UDP_8BIT_PORT; |
| Possibly guarded | no |
Dismissed: The guard `(ntohs(uh->source) & 0xFF00) == 0xF000` constrains source to [0xF000, 0xF0FF], making the subtraction result in [0,255] — exactly the u8 range. No counterexample can pass the guard and overflow.
mrp_pdu_parse_vecattr() — net/802/mrp.c FP confidence=high
mrp_pdu_parse_vecattr() reads valen from a network packet (genuinely server-supplied), but the while loop that uses valen as an iteration count is protected by a per-iteration call to skb_copy_bits(), which validates that [offset, offset+1) lies within skb->len before each byte read. A large valen cannot cause out-of-bounds memory access because the loop body will return -1 as soon as the skb is exhausted. No counterexample could be constructed that passes all guards yet causes OOB access.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | be16_to_cpu() line 703 |
| Taint snippet | valen = be16_to_cpu(get_unaligned(&mrp_cb(skb)->vah->lenflags) & |
| Tainted var | valen |
| Loop | while_loop line 729 |
| Sink snippet | while (valen > 0) { |
| Possibly guarded | yes (heuristic) |
Dismissed: Per-iteration protection via skb_copy_bits() at line 730 returns -1 if the skb is exhausted, terminating the loop safely. Could not construct a counterexample: any valen exceeding the remaining packet bytes causes skb_copy_bits() to return < 0 before any out-of-bounds read. The attrvalue buffer used in mrp_attrvalue_inc() is validated before the loop at lines 718-723 and is not affected by valen.
atm_dev_ioctl() — net/atm/resources.c FP confidence=high
All five findings are false positives. The 'size' variable in each case is set to a compile-time constant (strlen(dev->type)+1 from a kernel-internal string, ESI_LEN constant, sizeof(struct atm_dev_stats), sizeof(struct atm_cirange), sizeof(int)) or to a macro constant (ESI_LEN). None of these values are read from user space or a network buffer — they are kernel-internal computations. The static analyzer incorrectly propagated taint from the copy_to_user/copy_from_user call itself (treating the return value or associated variables as tainted) rather than identifying a genuine user-controlled size. The size arguments to all copy operations in this function are fixed compile-time constants or kernel-controlled values, not user-supplied sizes.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 237 |
| Taint snippet | if (copy_to_user(buf, dev->type, size)) { |
| Tainted var | size |
| Unvalidated size | copy_to_user() arg 2 line 237 — size size |
| Sink snippet | if (copy_to_user(buf, dev->type, size)) { |
| Possibly guarded | no |
Dismissed: size = strlen(dev->type) + 1 is computed from a kernel-internal string field (dev->type is set by the ATM driver, not user-supplied). The taint source is misidentified — the scanner appears to have tainted 'size' because it appears in the same copy_to_user call, not because it was read from user space. No genuine vulnerability.
Finding #2 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 244 |
| Taint snippet | if (copy_to_user(buf, dev->esi, size)) { |
| Tainted var | size |
| Unvalidated size | copy_to_user() arg 2 line 244 — size size |
| Sink snippet | if (copy_to_user(buf, dev->esi, size)) { |
| Possibly guarded | no |
Dismissed: size = ESI_LEN is a compile-time constant macro, not user-supplied. The scanner incorrectly propagated taint to 'size' from the copy_to_user call context. False positive.
Finding #3 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_from_user() line 268 |
| Taint snippet | if (copy_from_user(esi, buf, ESI_LEN)) { |
| Tainted var | ESI_LEN |
| Unvalidated size | copy_from_user() arg 2 line 268 — size ESI_LEN |
| Sink snippet | if (copy_from_user(esi, buf, ESI_LEN)) { |
| Possibly guarded | no |
Dismissed: ESI_LEN is a compile-time constant used as the size argument to copy_from_user. The destination 'esi' is a stack-allocated array of exactly ESI_LEN bytes. This is safe by construction. The scanner incorrectly flagged a constant as user-controlled.
Finding #4 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 290 |
| Taint snippet | if (copy_to_user(buf, &dev->ci_range, size)) { |
| Tainted var | size |
| Unvalidated size | copy_to_user() arg 2 line 290 — size size |
| Sink snippet | if (copy_to_user(buf, &dev->ci_range, size)) { |
| Possibly guarded | no |
Dismissed: size = sizeof(struct atm_cirange) is a compile-time constant. Not user-supplied. False positive.
Finding #5 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 297 |
| Taint snippet | if (copy_to_user(buf, &dev->link_rate, size)) { |
| Tainted var | size |
| Unvalidated size | copy_to_user() arg 2 line 297 — size size |
| Sink snippet | if (copy_to_user(buf, &dev->link_rate, size)) { |
| Possibly guarded | no |
Dismissed: size = sizeof(int) is a compile-time constant. Not user-supplied. False positive.
batadv_v_ogm_forward() — net/batman-adv/bat_v_ogm.c FP confidence=high
The scanner misidentifies ogm_forward as a pointer derived from an ntohs() offset. In reality, ogm_forward is cast from skb_buff, which is the base of a freshly allocated SKB data region of size BATADV_OGM2_HLEN + tvlv_len. The accessed fields (throughput, ttl) are within the fixed struct batadv_ogm2_packet header at offset 0, which is always fully present since BATADV_OGM2_HLEN is unconditionally added to the allocation. No OOB access is possible for the header field accesses. The taint propagates through packet_len but does not create a pointer offset by the server-supplied value.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 583 |
| Taint snippet | tvlv_len = ntohs(ogm_received->tvlv_len); |
| Tainted var | ogm_forward |
| Pointer deref | ogm_forward->throughput line 596 |
| Sink snippet | ogm_forward->throughput = htonl(neigh_ifinfo->bat_v.throughput); |
| Possibly guarded | no |
Dismissed: ogm_forward points to the start of a locally allocated SKB region of size BATADV_OGM2_HLEN+tvlv_len. Accessing ogm_forward->throughput is within the fixed header (BATADV_OGM2_HLEN bytes always allocated). Cannot construct a counterexample where tvlv_len causes this access to be OOB — the header is always present at offset 0.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 583 |
| Taint snippet | tvlv_len = ntohs(ogm_received->tvlv_len); |
| Tainted var | ogm_forward |
| Pointer deref | ogm_forward->ttl line 597 |
| Sink snippet | ogm_forward->ttl--; |
| Possibly guarded | no |
Dismissed: Same reasoning as finding #1. ogm_forward->ttl is within the fixed batadv_ogm2_packet header, always safely allocated. No counterexample possible.
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 583 |
| Taint snippet | tvlv_len = ntohs(ogm_received->tvlv_len); |
| Tainted var | ogm_forward |
| Pointer deref | ogm_forward->throughput line 601 |
| Sink snippet | if_outgoing->net_dev->name, ntohl(ogm_forward->throughput), |
| Possibly guarded | no |
Dismissed: Same as finding #1. Reading ogm_forward->throughput for debug output after it was already written at line 596 is safe for the same structural reasons.
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 583 |
| Taint snippet | tvlv_len = ntohs(ogm_received->tvlv_len); |
| Tainted var | ogm_forward |
| Pointer deref | ogm_forward->ttl line 602 |
| Sink snippet | ogm_forward->ttl, if_incoming->net_dev->name); |
| Possibly guarded | no |
Dismissed: Same as finding #2. Reading ogm_forward->ttl for debug output is safe; it is within the fixed-size header always present in the allocation.
batadv_v_ogm_process_per_outif() — net/batman-adv/bat_v_ogm.c BUG confidence=medium
The function passes a server-supplied tvlv_len directly to batadv_tvlv_containers_process() without validating that it fits within the actual remaining SKB buffer. The callee's internal checks only validate claimed lengths against each other, not against the true packet boundary.
Finding #1 — Category F — cross-function via batadv_tvlv_containers_process() — BUG oob_read
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 823 |
| Taint snippet | batadv_tvlv_containers_process(bat_priv, BATADV_OGM2, orig_node, |
| Tainted var | ntohs(ogm2->tvlv_len) |
| Call site | line 823 — passes ntohs(ogm2->tvlv_len) to batadv_tvlv_containers_process() |
| Call snippet | batadv_tvlv_containers_process(bat_priv, BATADV_OGM2, orig_node, |
| Loop | while_loop line 514 |
| Sink snippet | while (tvlv_value_len >= sizeof(*tvlv_hdr)) { |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in batadv_tvlv_containers_process when processing a malformed OGM2 packet with an inflated tvlv_len field, causing reads beyond the SKB data buffer
Fix: Before calling batadv_tvlv_containers_process(), validate that ntohs(ogm2->tvlv_len) does not exceed the actual remaining packet data: compute max_tvlv_len = skb->len - ogm_offset - sizeof(*ogm2) and drop/clamp if ntohs(ogm2->tvlv_len) > max_tvlv_len. Alternatively, perform this check in batadv_v_ogm_receive() before dispatching.
CVE pattern: Network packet length field not validated against actual buffer size before traversal
batadv_frag_insert_packet() — net/batman-adv/fragmentation.c FP confidence=high
The scanner flagged the use of a network-supplied seqno value as an array index, but the value is reduced by modulo BATADV_FRAG_BUFFER_COUNT before use. Since the array 'orig_node->fragments' has exactly BATADV_FRAG_BUFFER_COUNT elements, the modulo operation is a mathematically tight bounds guarantee. No counterexample exists: for any 16-bit seqno, seqno % BATADV_FRAG_BUFFER_COUNT is always in [0, BATADV_FRAG_BUFFER_COUNT-1]. The apparent 16→8 bit truncation is also safe because the modulo result fits in u8 regardless of input.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohs() line 162 |
| Taint snippet | bucket = seqno % BATADV_FRAG_BUFFER_COUNT; |
| Tainted var | bucket |
| Truncation | line 162: 16 → 8-bit u8 |
| Sink snippet | bucket = seqno % BATADV_FRAG_BUFFER_COUNT; |
| Possibly guarded | no |
Dismissed: seqno is network-supplied (u16), but 'seqno % BATADV_FRAG_BUFFER_COUNT' where BATADV_FRAG_BUFFER_COUNT=8 always yields a value in [0,7], well within u8 range. The truncation from 16-bit to 8-bit loses no information because the mathematical result is bounded. No counterexample can be constructed: any u16 modulo 8 is in [0,7].
Finding #2 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 161 |
| Taint snippet | seqno = ntohs(frag_packet->seqno); |
| Tainted var | bucket |
| Subscript | [] line 175 |
| Sink snippet | chain = &orig_node->fragments[bucket]; |
| Possibly guarded | no |
Dismissed: The array subscript 'bucket' is computed as 'seqno % BATADV_FRAG_BUFFER_COUNT'. This modulo operation is a perfect bounds check for the 'fragments[BATADV_FRAG_BUFFER_COUNT]' array: the result is always in [0, BATADV_FRAG_BUFFER_COUNT-1]. Attempted counterexample: seqno=65535 gives 65535%8=7, still valid. No OOB is possible regardless of the network-supplied seqno value.
batadv_frag_merge_packets() — net/batman-adv/fragmentation.c FP confidence=medium
The taint from ntohs(packet->total_size) is genuinely server-supplied and controls the ntail argument to pskb_expand_head(). However, the memcpy operations flagged inside pskb_expand_head() are not controlled by the tainted ntail value: the first memcpy (line 2320) copies skb_tail_pointer(skb)-skb->head bytes (old buffer geometry, not tainted size), and the second (line 2322) writes to data+size where size has been adjusted by SKB_WITH_OVERHEAD() on the actually-allocated buffer — both are bounded by real allocation outcomes. If ntail is negative (total_size < skb_out->len), kmalloc_reserve will likely fail and pskb_expand_head returns -ENOMEM, which the caller handles. No concrete counterexample produces an OOB write through these flagged memcpy paths.
Finding #1 — Category A — cross-function via pskb_expand_head() — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | ntohs() line 276 |
| Taint snippet | size = ntohs(packet->total_size) + hdr_size; |
| Tainted var | size |
| Call site | line 279 — passes size to pskb_expand_head() |
| Call snippet | if (pskb_expand_head(skb_out, 0, size - skb_out->len, GFP_ATOMIC) < 0) { |
| Sink (in callee) | memcpy() line 2320 (arg 0, role=pointer) |
| Sink snippet | memcpy(data + nhead, skb->head, skb_tail_pointer(skb) - skb->head); |
| Possibly guarded | no |
Dismissed: The memcpy at skbuff.c:2320 copies 'skb_tail_pointer(skb) - skb->head' bytes — this is the existing SKB data length, not derived from the tainted ntail/size argument. The tainted value only affects how much tail space is allocated; the copy is bounded by the old buffer's actual used length. No OOB write is possible through this path from the tainted server value.
Finding #2 — Category A — cross-function via pskb_expand_head() — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | ntohs() line 276 |
| Taint snippet | size = ntohs(packet->total_size) + hdr_size; |
| Tainted var | size |
| Call site | line 279 — passes size to pskb_expand_head() |
| Call snippet | if (pskb_expand_head(skb_out, 0, size - skb_out->len, GFP_ATOMIC) < 0) { |
| Sink (in callee) | memcpy() line 2322 (arg 0, role=pointer) |
| Sink snippet | memcpy((struct skb_shared_info *)(data + size), |
| Possibly guarded | no |
Dismissed: The memcpy at skbuff.c:2322 writes the skb_shared_info to 'data + size' where size = SKB_WITH_OVERHEAD(actually_allocated_buffer_size). This pointer arithmetic is derived from the real allocation result (post-kmalloc_reserve adjustment via SKB_WITH_OVERHEAD), not directly from the raw tainted ntail value. The skb_shared_info is always placed at the end of the allocated buffer by design. No exploitable OOB condition arises from the tainted server-supplied total_size field through this path.
batadv_recv_mcast_packet() — net/batman-adv/routing.c FP confidence=high
batadv_recv_mcast_packet() validates tvlv_buff_len against actual SKB data length (line 1367) and alignment (line 1373) before passing it to batadv_tvlv_containers_process(). The callee itself performs per-element bounds checking within the loop (lines 520-521, 526-527), ensuring no out-of-bounds access can occur even with adversarial tvlv_hdr->len values within the TVLV buffer. The validation discipline here is sound at both levels.
Finding #1 — Category F — cross-function via batadv_tvlv_containers_process() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 1365 |
| Taint snippet | tvlv_buff_len = ntohs(mcast_packet->tvlv_len); |
| Tainted var | tvlv_buff_len |
| Call site | line 1376 — passes tvlv_buff_len to batadv_tvlv_containers_process() |
| Call snippet | ret = batadv_tvlv_containers_process(bat_priv, BATADV_MCAST, NULL, skb, |
| Loop | while_loop line 514 |
| Sink snippet | while (tvlv_value_len >= sizeof(*tvlv_hdr)) { |
| Possibly guarded | yes (heuristic) |
Dismissed: Two-level validation is in place: (1) caller checks tvlv_buff_len <= skb->len - hdr_size before the call, bounding the region passed to the callee; (2) callee batadv_tvlv_containers_process() checks each inner tvlv_value_cont_len against remaining tvlv_value_len before advancing the pointer (lines 520-521) and also checks alignment (lines 526-527). No counterexample can be constructed that passes line 1367 yet causes OOB in the callee, because the callee's per-element checks prevent overrun within the already-bounded buffer.
batadv_recv_unicast_tvlv() — net/batman-adv/routing.c FP confidence=high
The function batadv_recv_unicast_tvlv() properly validates tvlv_buff_len against the actual skb payload before passing it to batadv_tvlv_containers_process(). The check at line 1121 ensures tvlv_buff_len <= skb->len - hdr_size, bounding it to actual received data. Inside batadv_tvlv_containers_process(), the while loop uses tvlv_value_len as an upper bound guard (not as a direct index), and every inner access is further bounded by per-element checks (tvlv_value_cont_len > tvlv_value_len). The loop cannot overread because it decrements tvlv_value_len at each step and checks before accessing. No counterexample can be constructed that passes both the outer check (line 1121) and causes OOB inside the callee.
Finding #1 — Category F — cross-function via batadv_tvlv_containers_process() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 1119 |
| Taint snippet | tvlv_buff_len = ntohs(unicast_tvlv_packet->tvlv_len); |
| Tainted var | tvlv_buff_len |
| Call site | line 1124 — passes tvlv_buff_len to batadv_tvlv_containers_process() |
| Call snippet | ret = batadv_tvlv_containers_process(bat_priv, BATADV_UNICAST_TVLV, |
| Loop | while_loop line 514 |
| Sink snippet | while (tvlv_value_len >= sizeof(*tvlv_hdr)) { |
| Possibly guarded | yes (heuristic) |
Dismissed: The outer guard at line 1121 (tvlv_buff_len > skb->len - hdr_size → goto free_skb) ensures tvlv_buff_len is at most the actual bytes available in the skb beyond the header. Inside batadv_tvlv_containers_process(), tvlv_value_len (which receives tvlv_buff_len) is used only as a loop termination condition (line 514: while tvlv_value_len >= sizeof(*tvlv_hdr)), not as a direct memory offset or allocation size. Each iteration subtracts sizeof(*tvlv_hdr) and then tvlv_value_cont_len from tvlv_value_len after checking tvlv_value_cont_len <= tvlv_value_len (line 520). This double-check pattern prevents any overread. No counterexample exists: any tvlv_buff_len that passes the outer check is bounded to real buffer data, and the inner per-element checks prevent advancing beyond that bound.
batadv_send_other_tt_response() — net/batman-adv/translation-table.c FP confidence=high
The flagged function batadv_tt_global_check_crc() is itself a validation/checking function. Its purpose is to validate that the CRC data in the received tt_vlan array (num_vlan entries) matches locally stored state. The loop inside it (line 2824) iterates over the vlan entries, but each iteration accesses tt_vlan_tmp = tt_vlan + i, which is a pointer into the tt_data->vlan_data array. The concern would be whether num_vlan could cause out-of-bounds access on tt_vlan. However, this function IS the validator — it is called specifically to check the received data. The accesses inside it are exactly the validation logic. Furthermore, the function name 'batadv_tt_global_check_crc' and its structure (it returns false on mismatch, true on success) make it clear this is a validation function, not a consumer. The loop bound concern is about whether tt_vlan has at least num_vlan entries, but since tt_vlan points into a received tvlv buffer, callers upstream are responsible for validating the buffer size. The static analysis treats internal accesses of a validator as sinks, which is a false positive per the stated rules.
Finding #1 — Category F — cross-function via batadv_tt_global_check_crc() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 3031 |
| Taint snippet | !batadv_tt_global_check_crc(req_dst_orig_node, tt_data->vlan_data, |
| Tainted var | ntohs(tt_data->num_vlan) |
| Call site | line 3031 — passes ntohs(tt_data->num_vlan) to batadv_tt_global_check_crc() |
| Call snippet | !batadv_tt_global_check_crc(req_dst_orig_node, tt_data->vlan_data, |
| Loop | for_loop line 2824 |
| Sink snippet | for (i = 0; i < num_vlan; i++) { |
| Possibly guarded | no |
Dismissed: batadv_tt_global_check_crc() is a validation function called specifically to validate the server-supplied tt_data. The loop inside it (iterating num_vlan times over tt_vlan) is the validation logic itself, not a dangerous sink. Per the analysis rules, accesses inside a validator function are part of validation logic and not vulnerable sinks. Additionally, the TVLV receive path in batman-adv typically validates the overall TVLV buffer length against the declared num_vlan before dispatching to this code path, though that validation occurs earlier in the receive chain. The finding is a false positive because the flagged sink is internal to the validator.
batadv_tvlv_container_ogm_append() — net/batman-adv/tvlv.c FP confidence=high
The tvlv container list is internally managed by the kernel (registered via batadv_tvlv_container_register with kernel-controlled lengths), not populated from received network packets. The buffer is pre-sized using batadv_tvlv_container_list_size() which iterates the same locked list summing the same ntohs(len) values, so the destination buffer is guaranteed to accommodate every memcpy. The spin_lock_bh held throughout prevents TOCTOU between sizing and copying.
Finding #1 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohs() line 365 |
| Taint snippet | memcpy(tvlv_value, tvlv + 1, ntohs(tvlv->tvlv_hdr.len)); |
| Tainted var | ntohs(tvlv->tvlv_hdr.len) |
| Sink | memcpy() line 365 (arg 2, role=size) |
| Sink snippet | memcpy(tvlv_value, tvlv + 1, ntohs(tvlv->tvlv_hdr.len)); |
| Possibly guarded | no |
Dismissed: ntohs(tvlv->tvlv_hdr.len) is not server-supplied; it comes from locally-registered tvlv containers. The buffer is allocated with batadv_tvlv_realloc_packet_buff() to hold exactly batadv_tvlv_container_list_size() bytes, which is computed by summing sizeof(batadv_tvlv_hdr)+ntohs(len) for each container under the same spinlock. No counterexample exists: any value of len that would cause OOB in the memcpy would have caused batadv_tvlv_container_list_size() to allocate a correspondingly larger buffer. The spin_lock_bh ensures the list cannot change between sizing and copying.
batadv_tvlv_container_register() — net/batman-adv/tvlv.c FP confidence=high
The struct tvlv_new is locally allocated and initialized by this function. The field tvlv_new->tvlv_hdr.len is set on line 250 via htons(tvlv_value_len), then immediately read back via ntohs() on line 252. This is a local round-trip, not a server-supplied value. The allocation on line 244 allocates exactly sizeof(*tvlv_new)+tvlv_value_len bytes, so the memcpy destination is correctly sized. The source buffer validity is an API contract between kernel callers (not a network input path).
Finding #1 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohs() line 252 |
| Taint snippet | memcpy(tvlv_new + 1, tvlv_value, ntohs(tvlv_new->tvlv_hdr.len)); |
| Tainted var | ntohs(tvlv_new->tvlv_hdr.len) |
| Sink | memcpy() line 252 (arg 2, role=size) |
| Sink snippet | memcpy(tvlv_new + 1, tvlv_value, ntohs(tvlv_new->tvlv_hdr.len)); |
| Possibly guarded | no |
Dismissed: The scanner incorrectly identifies ntohs() as a server-supplied taint source. In this function, tvlv_new is a locally-allocated struct, and tvlv_new->tvlv_hdr.len is written on line 250 with htons(tvlv_value_len) before being read back on line 252. The round-tripped value equals tvlv_value_len. The kzalloc on line 244 allocates sizeof(*tvlv_new)+tvlv_value_len bytes, so the destination of the memcpy (tvlv_new+1) has exactly tvlv_value_len bytes, matching the copy length. No counterexample can be constructed: any value that passes the allocation would give a destination large enough for the copy.
batadv_tvlv_ogm_receive() — net/batman-adv/tvlv.c FP confidence=medium
The server-supplied tvlv_value_len controls iteration in batadv_tvlv_containers_process(), but that function IS the parsing/validation logic — its per-element checks (line 520: tvlv_value_cont_len > tvlv_value_len, line 526: alignment check) ensure the loop stays within the declared tvlv_value_len boundary. The real question of whether tvlv_value_len is validated against the actual skb length belongs at the receive layer above batadv_ogm_receive(), which is outside the scope of this finding. The callee is a validator, not a vulnerable sink.
Finding #1 — Category F — cross-function via batadv_tvlv_containers_process() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 589 |
| Taint snippet | tvlv_value_len = ntohs(batadv_ogm_packet->tvlv_len); |
| Tainted var | tvlv_value_len |
| Call site | line 595 — passes tvlv_value_len to batadv_tvlv_containers_process() |
| Call snippet | batadv_tvlv_containers_process(bat_priv, BATADV_IV_OGM, orig_node, NULL, |
| Loop | while_loop line 514 |
| Sink snippet | while (tvlv_value_len >= sizeof(*tvlv_hdr)) { |
| Possibly guarded | no |
Dismissed: batadv_tvlv_containers_process() is the parsing/validation function — its internal loop uses tvlv_value_len as the declared buffer bound and validates each per-element length against remaining tvlv_value_len before advancing (line 520). No counterexample can be constructed that passes the per-element guard while causing OOB within the declared boundary. Whether tvlv_value_len is validated against the actual skb data length is a concern in the outer receive path, not in this function. The scanner misidentifies the validator's internal loop as a vulnerable sink.
lowpan_control_write() — net/bluetooth/6lowpan.c FP confidence=high
The flagged 'buf_size' is computed entirely within the kernel as min(count, sizeof(buf) - 1), where sizeof(buf) is 32 (a compile-time constant). The result is therefore clamped to at most 31, which is strictly less than the 32-byte destination buffer. No user-supplied value is used as the size argument to copy_from_user without being bounded first. The scanner appears to have propagated taint from the user-supplied 'count' through the min() call and incorrectly concluded the result is unbounded.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_from_user() line 1121 |
| Taint snippet | if (copy_from_user(buf, user_buffer, buf_size)) |
| Tainted var | buf_size |
| Unvalidated size | copy_from_user() arg 2 line 1121 — size buf_size |
| Sink snippet | if (copy_from_user(buf, user_buffer, buf_size)) |
| Possibly guarded | no |
Dismissed: buf_size = min(count, sizeof(buf) - 1) = min(count, 31). The result is always <= 31 < 32 (sizeof(buf)), so copy_from_user can never overflow the 32-byte stack buffer. No counterexample can be constructed because min() provides an absolute upper bound of 31. This is a classic false positive from taint propagation through min() without recognising that the second operand is a compile-time constant less than the buffer size.
bnep_ctrl_set_mcfilter() — net/bluetooth/bnep/core.c FP confidence=high
The function validates len >= n (raw byte count) before dividing n by ETH_ALEN*2=12 to get the iteration count. Since n_iterations * 12 <= n_original <= len, the loop cannot read beyond the validated buffer. The bounds check at line 157 is sufficient when combined with the integer division at line 163.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | get_unaligned_be16() line 153 |
| Taint snippet | n = get_unaligned_be16(data); |
| Tainted var | n |
| Loop | for_loop line 174 |
| Sink snippet | for (; n > 0; n--) { |
| Possibly guarded | yes (heuristic) |
Dismissed: The pre-loop check `if (len < n) return -EILSEQ` ensures the raw byte count n fits in the buffer. After `n /= (ETH_ALEN * 2)`, the loop runs n_new = n_original/12 times, each consuming 12 bytes, for a total of n_new*12 <= n_original <= len bytes. No counterexample can be constructed: any values passing the guard satisfy n_iterations*12 <= len. The scanner flagged this as 'possibly guarded' and the guard is in fact sufficient.
bnep_ctrl_set_netfilter() — net/bluetooth/bnep/core.c FP confidence=high
The function performs correct two-level validation: (1) packet buffer bounds checked at line 111 before the loop (original n bytes available), (2) array bounds checked at line 118 (n/4 entries <= BNEP_MAX_PROTO_FILTERS). The loop reads exactly (n/4)*4 bytes which is <= original n bytes validated against len. No counterexample is constructable.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | get_unaligned_be16() line 107 |
| Taint snippet | n = get_unaligned_be16(data); |
| Tainted var | n |
| Loop | for_loop line 122 |
| Sink snippet | for (i = 0; i < n; i++) { |
| Possibly guarded | yes (heuristic) |
Dismissed: Two guards protect the loop: (a) line 111 checks len >= n (original byte count), ensuring the packet buffer has enough data for the loop to read (n/4)*4 <= n bytes; (b) line 118 checks n/4 <= BNEP_MAX_PROTO_FILTERS, bounding array index. No counterexample can be constructed that passes both guards yet causes OOB. False positive.
hci_get_conn_list() — net/bluetooth/hci_conn.c FP confidence=high
The 'size' variable used in copy_to_user() is not user-controlled at the point of the call. Although req.conn_num is read from user space, it is validated at line 2773 (must be non-zero and ≤ (PAGE_SIZE*2)/sizeof(*ci)). The allocation at line 2778 uses sizeof(req) + req.conn_num * sizeof(*ci), which is safe given that bound. At line 2805, size is RECOMPUTED as sizeof(req) + n * sizeof(*ci), where n is the kernel-internal counter of actual connections found (capped at req.conn_num by the break at line 2798). So n ≤ req.conn_num, and the new size ≤ the originally allocated size. The buffer 'cl' has capacity for the original (larger) size, so copying the smaller recomputed size is strictly within the allocated region. The copy_to_user destination 'arg' is a user pointer; the kernel copies at most as much as the user asked for (bounded by the validated req.conn_num), so there is no overflow of the user buffer beyond what the user explicitly declared it can hold.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 2809 |
| Taint snippet | err = copy_to_user(arg, cl, size); |
| Tainted var | size |
| Unvalidated size | copy_to_user() arg 2 line 2809 — size size |
| Sink snippet | err = copy_to_user(arg, cl, size); |
| Possibly guarded | no |
Dismissed: The user-supplied req.conn_num is validated at line 2773 against (PAGE_SIZE*2)/sizeof(*ci). The 'size' passed to copy_to_user is sizeof(req)+n*sizeof(*ci) where n≤req.conn_num, so no counterexample exists: any value of req.conn_num that passes the guard produces a size that both fits in the allocation and is within the user's declared buffer. False positive.
force_no_mitm_write() — net/bluetooth/hci_debugfs.c FP confidence=high
The scanner misidentifies the taint source. buf_size is computed as min(count, sizeof(buf)-1) where sizeof(buf) is 32 (compile-time constant). The count parameter is user-supplied (from the VFS write syscall), but buf_size is immediately clamped to at most 31 via the min() call before copy_from_user is invoked. This is the standard safe pattern for debugfs write handlers. The scanner erroneously traces buf_size as tainted from copy_from_user's return value, but buf_size is actually computed BEFORE copy_from_user is called and is bounded by the stack buffer size.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_from_user() line 1168 |
| Taint snippet | if (copy_from_user(buf, user_buf, buf_size)) |
| Tainted var | buf_size |
| Unvalidated size | copy_from_user() arg 2 line 1168 — size buf_size |
| Sink snippet | if (copy_from_user(buf, user_buf, buf_size)) |
| Possibly guarded | no |
Dismissed: buf_size = min(count, sizeof(buf) - 1) = min(count, 31). This clamps the copy size to at most 31 bytes, well within the 32-byte stack buffer. No counterexample is possible: any value of count (including SIZE_MAX) results in buf_size <= 31, and buf[buf_size] = '\0' writes at most to index 31, which is within bounds. The scanner appears to have confused the output of copy_from_user (the taint introduced into 'buf') with the size argument 'buf_size', which is pre-computed and bounded before the call.
hci_le_per_adv_report_evt() — net/bluetooth/hci_event.c FP confidence=high
The scanner incorrectly propagates taint from le16_to_cpu(ev->sync_handle) through hci_conn_hash_lookup_pa_sync_handle() to its return value pa_sync. The sync_handle field is used only as a lookup key into the kernel's internal hci_conn hash table. The returned pa_sync pointer is a kernel-allocated, kernel-managed struct hci_conn object — not a pointer derived from the server-supplied handle value. The null check at line 6620 further ensures safety. All field accesses in mgmt_device_connected() operate on legitimate kernel data structures, not attacker-controlled memory.
Finding #1 — Category E — cross-function via mgmt_device_connected() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 6616 |
| Taint snippet | pa_sync = hci_conn_hash_lookup_pa_sync_handle |
| Tainted var | pa_sync |
| Call site | line 6629 — passes pa_sync to mgmt_device_connected() |
| Call snippet | mgmt_device_connected(hdev, pa_sync, NULL, 0); |
| Pointer deref | pa_sync-> line 9897 |
| Sink snippet | bacpy(&ev->addr.bdaddr, &conn->dst); |
| Possibly guarded | no |
Dismissed: pa_sync is returned by hci_conn_hash_lookup_pa_sync_handle() — a hash table lookup using sync_handle as a key. The returned pointer is a kernel-managed struct hci_conn, not a pointer derived from the server-supplied handle value. The null check guards against no-match case. bacpy() on conn->dst accesses a legitimate kernel struct field.
Finding #2 — Category E — cross-function via mgmt_device_connected() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 6616 |
| Taint snippet | pa_sync = hci_conn_hash_lookup_pa_sync_handle |
| Tainted var | pa_sync |
| Call site | line 6629 — passes pa_sync to mgmt_device_connected() |
| Call snippet | mgmt_device_connected(hdev, pa_sync, NULL, 0); |
| Pointer deref | pa_sync-> line 9898 |
| Sink snippet | ev->addr.type = link_to_bdaddr(conn->type, conn->dst_type); |
| Possibly guarded | no |
Dismissed: Same false taint propagation through hash lookup. conn->type and conn->dst_type are fields of a kernel-allocated struct hci_conn, not attacker-controlled.
Finding #3 — Category E — cross-function via mgmt_device_connected() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 6616 |
| Taint snippet | pa_sync = hci_conn_hash_lookup_pa_sync_handle |
| Tainted var | pa_sync |
| Call site | line 6629 — passes pa_sync to mgmt_device_connected() |
| Call snippet | mgmt_device_connected(hdev, pa_sync, NULL, 0); |
| Pointer deref | pa_sync-> line 9903 |
| Sink snippet | ev->flags = __cpu_to_le32(flags); |
| Possibly guarded | no |
Dismissed: Same false taint propagation. ev->flags is written with a locally-computed flags variable, not with any server-supplied data. The sink is writing TO ev (local skb data), not reading from conn in a dangerous way.
Finding #4 — Category B — cross-function via mgmt_device_connected() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 6616 |
| Taint snippet | pa_sync = hci_conn_hash_lookup_pa_sync_handle |
| Tainted var | pa_sync |
| Call site | line 6629 — passes pa_sync to mgmt_device_connected() |
| Call snippet | mgmt_device_connected(hdev, pa_sync, NULL, 0); |
| Sink (in callee) | memcmp() line 9916 (arg 2, role=size) |
| Sink snippet | if (memcmp(conn->dev_class, "\0\0\0", sizeof(conn->dev_class))) |
| Possibly guarded | yes (heuristic) |
Dismissed: memcmp() size argument is sizeof(conn->dev_class) — a compile-time constant, not any server-supplied value. The scanner misidentified the taint role. False positive.
Finding #5 — Category E — cross-function via mgmt_device_connected() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 6616 |
| Taint snippet | pa_sync = hci_conn_hash_lookup_pa_sync_handle |
| Tainted var | pa_sync |
| Call site | line 6629 — passes pa_sync to mgmt_device_connected() |
| Call snippet | mgmt_device_connected(hdev, pa_sync, NULL, 0); |
| Pointer deref | pa_sync-> line 9921 |
| Sink snippet | ev->eir_len = cpu_to_le16(eir_len); |
| Possibly guarded | no |
Dismissed: ev->eir_len is written with locally-computed eir_len, not any server-supplied data. The conn pointer is a kernel-managed struct hci_conn from the hash table lookup, not a server-supplied pointer.
hci_sock_sendmsg() — net/bluetooth/hci_sock.c FP confidence=high
The function properly validates user-supplied data before dangerous operations. The ogf array subscript is protected by C short-circuit evaluation: the array access only occurs in the right-hand side of a || expression, which is only evaluated when ogf <= HCI_SFLT_MAX_OGF, making the array access always in-bounds. The static scanner apparently did not model C's short-circuit evaluation semantics correctly.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | get_unaligned_le16() line 1879 |
| Taint snippet | u16 opcode = get_unaligned_le16(skb->data); |
| Tainted var | ogf |
| Subscript | [] line 1885 |
| Sink snippet | &hci_sec_filter.ocf_mask[ogf])) && |
| Possibly guarded | yes (heuristic) |
Dismissed: C's short-circuit OR evaluation ensures hci_sec_filter.ocf_mask[ogf] is only accessed when (ogf > HCI_SFLT_MAX_OGF) is false, i.e., when ogf <= HCI_SFLT_MAX_OGF. Since the array is sized for HCI_SFLT_MAX_OGF+1 elements, no OOB access is possible. No counterexample can be constructed — any ogf > HCI_SFLT_MAX_OGF causes the || short-circuit to skip the array access, and any ogf <= HCI_SFLT_MAX_OGF is a valid index. The scanner did not model short-circuit evaluation, producing a false positive.
send_hci_cmd_sync() — net/bluetooth/mgmt.c FP confidence=high
The taint tracker incorrectly propagates taint from cp->opcode (passed as input to __hci_cmd_sync_ev()) through to the function's return value skb. The returned sk_buff is a kernel-allocated buffer whose pointer is not derived from cp->opcode. Even if taint were legitimate: (1) mgmt_status() validates its argument with ARRAY_SIZE bounds before indexing; (2) mgmt_cmd_complete() allocates the destination buffer with rp_len included in the allocation size before memcpy, making it safe by construction; (3) the struct field writes in mgmt_cmd_complete() (hdr->, ev->) are to locally-allocated buffers, not indexed by any tainted value.
Finding #1 — Category C — cross-function via mgmt_status() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 2637 |
| Taint snippet | skb = __hci_cmd_sync_ev(hdev, le16_to_cpu(cp->opcode), |
| Tainted var | skb |
| Call site | line 2644 — passes skb to mgmt_status() |
| Call snippet | mgmt_status(PTR_ERR(skb))); |
| Subscript (in callee) | [] line 315 |
| Sink snippet | return mgmt_status_table[err]; |
| Possibly guarded | yes (heuristic) |
Dismissed: skb is the return value of __hci_cmd_sync_ev(), not derived from cp->opcode. PTR_ERR(skb) yields a small negative errno. mgmt_status() checks err<0 (routes to mgmt_errno_status) and err<ARRAY_SIZE(mgmt_status_table) before indexing — no counterexample possible. False positive.
Finding #2 — Category E — cross-function via mgmt_cmd_complete() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 2637 |
| Taint snippet | skb = __hci_cmd_sync_ev(hdev, le16_to_cpu(cp->opcode), |
| Tainted var | skb |
| Call site | line 2648 — passes skb to mgmt_cmd_complete() |
| Call snippet | mgmt_cmd_complete(cmd->sk, hdev->id, MGMT_OP_HCI_CMD_SYNC, 0, |
| Pointer deref | skb-> line 182 |
| Sink snippet | hdr->opcode = cpu_to_le16(MGMT_EV_CMD_COMPLETE); |
| Possibly guarded | no |
Dismissed: Taint incorrectly propagated from cp->opcode through __hci_cmd_sync_ev() return value to skb. hdr is allocated locally in mgmt_cmd_complete() via alloc_skb/skb_put — writing to hdr->opcode is not a tainted dereference. False positive from taint tracker misattribution.
Finding #3 — Category E — cross-function via mgmt_cmd_complete() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 2637 |
| Taint snippet | skb = __hci_cmd_sync_ev(hdev, le16_to_cpu(cp->opcode), |
| Tainted var | skb |
| Call site | line 2648 — passes skb to mgmt_cmd_complete() |
| Call snippet | mgmt_cmd_complete(cmd->sk, hdev->id, MGMT_OP_HCI_CMD_SYNC, 0, |
| Pointer deref | skb-> line 183 |
| Sink snippet | hdr->index = cpu_to_le16(index); |
| Possibly guarded | no |
Dismissed: Same false taint propagation as #2. hdr->index write targets a locally allocated buffer unrelated to skb content or cp->opcode value. False positive.
Finding #4 — Category E — cross-function via mgmt_cmd_complete() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 2637 |
| Taint snippet | skb = __hci_cmd_sync_ev(hdev, le16_to_cpu(cp->opcode), |
| Tainted var | skb |
| Call site | line 2648 — passes skb to mgmt_cmd_complete() |
| Call snippet | mgmt_cmd_complete(cmd->sk, hdev->id, MGMT_OP_HCI_CMD_SYNC, 0, |
| Pointer deref | skb-> line 184 |
| Sink snippet | hdr->len = cpu_to_le16(sizeof(*ev) + rp_len); |
| Possibly guarded | no |
Dismissed: Same false taint propagation. hdr->len write uses sizeof(*ev)+rp_len where rp_len=skb->len is used consistently in alloc_skb and skb_put. False positive.
Finding #5 — Category E — cross-function via mgmt_cmd_complete() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 2637 |
| Taint snippet | skb = __hci_cmd_sync_ev(hdev, le16_to_cpu(cp->opcode), |
| Tainted var | skb |
| Call site | line 2648 — passes skb to mgmt_cmd_complete() |
| Call snippet | mgmt_cmd_complete(cmd->sk, hdev->id, MGMT_OP_HCI_CMD_SYNC, 0, |
| Pointer deref | skb-> line 187 |
| Sink snippet | ev->opcode = cpu_to_le16(cmd); |
| Possibly guarded | no |
Dismissed: Same false taint propagation. ev->opcode write targets locally allocated struct. False positive.
Finding #6 — Category E — cross-function via mgmt_cmd_complete() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 2637 |
| Taint snippet | skb = __hci_cmd_sync_ev(hdev, le16_to_cpu(cp->opcode), |
| Tainted var | skb |
| Call site | line 2648 — passes skb to mgmt_cmd_complete() |
| Call snippet | mgmt_cmd_complete(cmd->sk, hdev->id, MGMT_OP_HCI_CMD_SYNC, 0, |
| Pointer deref | skb-> line 188 |
| Sink snippet | ev->status = status; |
| Possibly guarded | no |
Dismissed: Same false taint propagation. ev->status write targets locally allocated struct. False positive.
Finding #7 — Category B — cross-function via mgmt_cmd_complete() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 2637 |
| Taint snippet | skb = __hci_cmd_sync_ev(hdev, le16_to_cpu(cp->opcode), |
| Tainted var | skb |
| Call site | line 2648 — passes skb to mgmt_cmd_complete() |
| Call snippet | mgmt_cmd_complete(cmd->sk, hdev->id, MGMT_OP_HCI_CMD_SYNC, 0, |
| Sink (in callee) | memcpy() line 191 (arg 2, role=size) |
| Sink snippet | memcpy(ev->data, rp, rp_len); |
| Possibly guarded | no |
Dismissed: rp_len=skb->len is used as memcpy size, but the destination buffer was allocated as alloc_skb(sizeof(*hdr)+sizeof(*ev)+rp_len) — the allocation already accounts for rp_len bytes. No counterexample possible for OOB. False positive.
Finding #8 — Category E — cross-function via mgmt_cmd_complete() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 2637 |
| Taint snippet | skb = __hci_cmd_sync_ev(hdev, le16_to_cpu(cp->opcode), |
| Tainted var | skb |
| Call site | line 2648 — passes skb to mgmt_cmd_complete() |
| Call snippet | mgmt_cmd_complete(cmd->sk, hdev->id, MGMT_OP_HCI_CMD_SYNC, 0, |
| Pointer deref | skb-> line 191 |
| Sink snippet | memcpy(ev->data, rp, rp_len); |
| Possibly guarded | no |
Dismissed: ev->data is the destination of memcpy, allocated with sufficient size as noted in #7. False positive.
Finding #9 — Category E — cross-function via mgmt_cmd_complete() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 2637 |
| Taint snippet | skb = __hci_cmd_sync_ev(hdev, le16_to_cpu(cp->opcode), |
| Tainted var | skb |
| Call site | line 2648 — passes skb to mgmt_cmd_complete() |
| Call snippet | mgmt_cmd_complete(cmd->sk, hdev->id, MGMT_OP_HCI_CMD_SYNC, 0, |
| Pointer deref | skb-> line 193 |
| Sink snippet | mskb = create_monitor_ctrl_event(hdr->index, hci_sock_get_cookie(sk), |
| Possibly guarded | no |
Dismissed: hdr->index dereference targets locally allocated hdr struct. Taint incorrectly propagated. False positive.
Finding #10 — Category E — cross-function via mgmt_cmd_complete() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 2637 |
| Taint snippet | skb = __hci_cmd_sync_ev(hdev, le16_to_cpu(cp->opcode), |
| Tainted var | skb |
| Call site | line 2648 — passes skb to mgmt_cmd_complete() |
| Call snippet | mgmt_cmd_complete(cmd->sk, hdev->id, MGMT_OP_HCI_CMD_SYNC, 0, |
| Pointer deref | skb-> line 197 |
| Sink snippet | skb->tstamp = mskb->tstamp; |
| Possibly guarded | no |
Dismissed: skb->tstamp at line 197 refers to the locally allocated skb in mgmt_cmd_complete(), not the skb from __hci_cmd_sync_ev(). The taint tracker has completely lost track of which skb is which. False positive.
bpf_ctx_finish() — net/bpf/test_run.c FP confidence=high
The copy_size variable in bpf_ctx_finish() is NOT user-controlled in a dangerous way. It is computed as min(size, kattr->test.ctx_size_out) where 'size' is the kernel-internal size of the context structure passed by the caller (e.g., sizeof(struct xdp_md), sizeof(struct bpf_flow_keys), sizeof(*user_ctx)) — a compile-time constant. The 'data' buffer pointed to by the 'data' parameter holds exactly 'size' bytes. The copy_size is then clamped at line 896-899 to kattr->test.ctx_size_out (the user-supplied output buffer size). Since copy_size <= size (the actual data buffer size) and copy_size <= kattr->test.ctx_size_out (the user output buffer size), both source and destination are correctly bounded. The scanner flagged copy_size as tainted because kattr->test.ctx_size_out is user-supplied, but the clamping logic at line 896-899 ensures copy_size never exceeds either buffer. No counterexample can be constructed that passes the guard (copy_size = min(size, ctx_size_out) <= size = actual data length) and causes OOB.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 901 |
| Taint snippet | if (copy_to_user(data_out, data, copy_size)) |
| Tainted var | copy_size |
| Unvalidated size | copy_to_user() arg 2 line 901 — size copy_size |
| Sink snippet | if (copy_to_user(data_out, data, copy_size)) |
| Possibly guarded | no |
Dismissed: copy_size starts as 'size' (kernel-internal, caller-supplied compile-time sizeof), then is clamped to min(size, kattr->test.ctx_size_out) at lines 896-899. This means copy_size <= size (so no read past the 'data' buffer) and copy_size <= ctx_size_out (so no write past the user output buffer). No counterexample exists: any value of ctx_size_out either leaves copy_size == size (no truncation) or reduces it further. The false positive arises because the taint analysis tracks ctx_size_out as user-controlled and propagates it into copy_size without recognizing the min() clamping semantics.
bpf_ctx_init() — net/bpf/test_run.c FP confidence=high
The tainted variable 'size' (kattr->test.ctx_size_in) is user-supplied, but it is explicitly bounded by max_size via min_t(u32, max_size, size) at line 876 before being passed to copy_from_user(). The destination buffer 'data' was allocated with kzalloc(max_size) at line 865. Therefore the copy length is at most max_size, which is exactly the buffer size. Additionally, bpf_check_uarg_tail_zero() is called first to validate that any bytes in data_in beyond min(max_size, size) are zero, providing additional safety. The scanner missed the min_t() clamping at line 876 which is the definitive bound enforcement.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_from_user() line 877 |
| Taint snippet | if (copy_from_user(data, data_in, size)) { |
| Tainted var | size |
| Unvalidated size | copy_from_user() arg 2 line 877 — size size |
| Sink snippet | if (copy_from_user(data, data_in, size)) { |
| Possibly guarded | no |
Dismissed: The user-supplied 'size' (ctx_size_in) is clamped at line 876 via 'size = min_t(u32, max_size, size)' before use in copy_from_user(). The destination buffer is kzalloc(max_size), so the copy length after clamping is at most max_size bytes — exactly the buffer capacity. No counterexample exists: any value of size, whether 0, max_size, or UINT_MAX, results in a copy of at most max_size bytes into a max_size-byte buffer. This is a false positive.
bpf_prog_test_run_skb() — net/bpf/test_run.c FP confidence=high
The function carefully bounds data_len to PAGE_SIZE via min_t() before using it as the size argument to copy_from_user(), and the destination buffer is always a single page (PAGE_SIZE bytes) allocated by alloc_page(). The validation is sufficient and no OOB is possible.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_from_user() line 1149 |
| Taint snippet | if (copy_from_user(page_address(page), data_in + copied, |
| Tainted var | data_len |
| Unvalidated size | copy_from_user() arg 2 line 1149 — size data_len |
| Sink snippet | if (copy_from_user(page_address(page), data_in + copied, |
| Possibly guarded | no |
Dismissed: data_len is computed as min_t(u32, kattr->test.data_size_in - copied, PAGE_SIZE), which clamps it to at most PAGE_SIZE. The destination buffer is page_address(page) where page is allocated by alloc_page(GFP_KERNEL), which always provides exactly PAGE_SIZE bytes. No counterexample can be constructed because data_len <= PAGE_SIZE == destination size. The scanner incorrectly flagged user-origin taint without recognizing the min_t() clamp; this is a false positive.
bpf_prog_test_run_xdp() — net/bpf/test_run.c FP confidence=high
The function reads user-supplied kattr->test.data_size_in, but data_len is bounded by min_t(u32, ..., PAGE_SIZE) before being used as the copy_from_user size argument. The destination page is always PAGE_SIZE bytes (alloc_page), so the copy is safe. The scanner incorrectly propagated user-taint through the min_t clamp without recognizing it as a sufficient bound.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_from_user() line 1448 |
| Taint snippet | if (copy_from_user(page_address(page), data_in + size, |
| Tainted var | data_len |
| Unvalidated size | copy_from_user() arg 2 line 1448 — size data_len |
| Sink snippet | if (copy_from_user(page_address(page), data_in + size, |
| Possibly guarded | no |
Dismissed: data_len = min_t(u32, kattr->test.data_size_in - size, PAGE_SIZE) ensures data_len <= PAGE_SIZE. The destination is alloc_page() which provides exactly PAGE_SIZE bytes. No counterexample can be constructed where data_len > PAGE_SIZE, so the copy cannot overflow. This is a false positive caused by the taint analysis propagating user-supplied taint through a min_t clamp that effectively sanitizes the value.
bpf_test_finish() — net/bpf/test_run.c FP confidence=high
Both findings are false positives. The tainted values 'len' and 'data_len' are NOT user-supplied sizes — they are kernel-internal computations bounded by min() operations against kernel-controlled values. 'len' is min(copy_size, head_len) where copy_size is bounded by kattr->test.data_size_out (user-supplied but only used as a clamp, not as a direct copy size) and head_len = size - frag_size (kernel-computed from skb metadata). 'data_len' is min_t(u32, copy_size - offset, skb_frag_size(frag)) where both arguments are bounded: copy_size - offset is bounded by copy_size (which is itself bounded by the user's data_size_out or the kernel's size parameter), and skb_frag_size(frag) is the actual fragment size. The destination buffer is user-space (data_out) which is a user pointer, so copy_to_user is the correct mechanism. The copy sizes are strictly bounded by the minimum of the user's declared output buffer size and the actual data available. No OOB write to a kernel buffer is possible.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 458 |
| Taint snippet | if (copy_to_user(data_out, data, len)) |
| Tainted var | len |
| Unvalidated size | copy_to_user() arg 2 line 458 — size len |
| Sink snippet | if (copy_to_user(data_out, data, len)) |
| Possibly guarded | no |
Dismissed: len = min(copy_size, head_len). copy_size is either the original kernel-computed 'size' parameter or clamped to kattr->test.data_size_out (the user's declared output buffer size). head_len = size - frag_size is kernel-computed from skb metadata. The min() ensures len never exceeds either the actual data available or the user's declared buffer capacity. copy_to_user writes to user-space (data_out), which is the correct target — no kernel buffer overflow is possible. Counterexample attempt: even if data_size_out is very large, copy_size is clamped to size (the actual data length), so len <= size = head_len + frag_size, and since len = min(copy_size, head_len) <= head_len, the copy only covers the linear portion of the skb. No counterexample found.
Finding #2 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 476 |
| Taint snippet | if (copy_to_user(data_out + offset, |
| Tainted var | data_len |
| Unvalidated size | copy_to_user() arg 2 line 476 — size data_len |
| Sink snippet | if (copy_to_user(data_out + offset, |
| Possibly guarded | no |
Dismissed: data_len = min_t(u32, copy_size - offset, skb_frag_size(frag)). The loop guards ensure offset < copy_size before computing data_len, so copy_size - offset > 0. skb_frag_size(frag) is the kernel-managed actual fragment size. The min_t ensures data_len does not exceed either the remaining output buffer space or the actual fragment data. Additionally, after each iteration offset += data_len, maintaining the invariant that offset <= copy_size. copy_to_user writes to user-space at data_out + offset, which is within the user's declared output region. No kernel buffer is written. Counterexample attempt: for data_len to overflow, one would need copy_size - offset or skb_frag_size to be adversarially large, but both are bounded by kernel-internal values (copy_size is bounded by the actual data size, and skb_frag_size reflects real fragment sizes). No counterexample found.
br_ioctl_stub() — net/bridge/br_ioctl.c FP confidence=high
The scanner has misidentified IFNAMSIZ as a user-controlled taint source. IFNAMSIZ is a compile-time kernel constant (16), not derived from user input. The copy_from_user() call uses a fixed-size stack buffer 'buf[IFNAMSIZ]' as the destination and IFNAMSIZ as the copy length — both are kernel-internal constants. This is a standard safe pattern for copying a fixed-size interface name from userspace.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_from_user() line 435 |
| Taint snippet | if (copy_from_user(buf, uarg, IFNAMSIZ)) { |
| Tainted var | IFNAMSIZ |
| Unvalidated size | copy_from_user() arg 2 line 435 — size IFNAMSIZ |
| Sink snippet | if (copy_from_user(buf, uarg, IFNAMSIZ)) { |
| Possibly guarded | no |
Dismissed: IFNAMSIZ is a compile-time constant (defined as 16 in the kernel headers), not derived from user input in any way. The taint source label 'copy_from_user() line 435' is the call itself, but the scanner has confused the constant size argument IFNAMSIZ with a tainted value. The destination buffer 'buf' is declared as 'char buf[IFNAMSIZ]' on the stack, and the copy length is exactly IFNAMSIZ — perfectly matched. Additionally, buf[IFNAMSIZ-1] = 0 ensures null-termination after the copy. No counterexample can be constructed because there is no variable involved in the size argument. This is a classic false positive from a taint analysis tool that incorrectly propagates taint through copy_from_user() to the constant size argument.
old_deviceless() — net/bridge/br_ioctl.c FP confidence=high
The scanner flagged IFNAMSIZ as a user-controlled size, but IFNAMSIZ is a kernel compile-time constant (16), not a value read from user space. The size argument to copy_from_user() is completely internal and not tainted by user data. The destination buffer 'buf' is declared as char buf[IFNAMSIZ], so the copy is exactly safe: IFNAMSIZ bytes copied into an IFNAMSIZ-byte stack buffer. This is a classic false positive from a taint analysis tool that incorrectly propagated taint through the macro/constant rather than recognizing it as a kernel-internal constant.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_from_user() line 382 |
| Taint snippet | if (copy_from_user(buf, argp, IFNAMSIZ)) |
| Tainted var | IFNAMSIZ |
| Unvalidated size | copy_from_user() arg 2 line 382 — size IFNAMSIZ |
| Sink snippet | if (copy_from_user(buf, argp, IFNAMSIZ)) |
| Possibly guarded | no |
Dismissed: IFNAMSIZ is a kernel compile-time constant (#define IFNAMSIZ 16), not a user-supplied value. The taint analysis tool incorrectly labeled it as user-controlled. The destination buffer 'buf' is char buf[IFNAMSIZ] on the stack, and the copy uses exactly IFNAMSIZ as the length, so there is no overflow. No counterexample can be constructed because the size is not variable. This is a false positive.
br_ip4_multicast_igmp3_report() — net/bridge/br_multicast.c FP confidence=high
The function uses per-iteration ip_mc_may_pull() bounds checks that validate the cumulative 'len' before each access. Finding #1 is protected by per-iteration checks (form b). Findings #2-7 involve grec reloaded at line 2921 to a position already validated by both pull checks. Finding #8 involves nsrcs passed to a callee where the source array srcs was already bounds-checked to contain nsrcs*4 bytes. No genuine vulnerabilities exist.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 2861 |
| Taint snippet | num = ntohs(ih->ngrec); |
| Tainted var | num |
| Loop | for_loop line 2864 |
| Sink snippet | for (i = 0; i < num; i++) { |
| Possibly guarded | no |
Dismissed: Per-iteration bounds checks via ip_mc_may_pull() at lines 2866-2867 and 2875-2876 terminate the loop early if 'num' is too large for the packet. No counterexample possible: any iteration beyond packet bounds returns -EINVAL.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 2872 |
| Taint snippet | nsrcs = ntohs(grec->grec_nsrcs); |
| Tainted var | grec |
| Pointer deref | grec->grec_src line 2926 |
| Sink snippet | grec->grec_src, |
| Possibly guarded | no |
Dismissed: grec is reloaded at line 2921 to skb->data + (len - sizeof(*grec) - nsrcs*4). At this point len was validated by ip_mc_may_pull to include sizeof(*grec) + nsrcs*4 bytes, so grec->grec_src (immediately after grec in memory) is within the validated region.
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 2872 |
| Taint snippet | nsrcs = ntohs(grec->grec_nsrcs); |
| Tainted var | grec |
| Pointer deref | grec->grec_src line 2931 |
| Sink snippet | grec->grec_src, |
| Possibly guarded | no |
Dismissed: Same as finding #2. The grec->grec_src access at line 2931 is covered by the same ip_mc_may_pull validation establishing that nsrcs*4 bytes after grec are accessible.
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 2872 |
| Taint snippet | nsrcs = ntohs(grec->grec_nsrcs); |
| Tainted var | grec |
| Pointer deref | grec->grec_src line 2936 |
| Sink snippet | grec->grec_src, |
| Possibly guarded | no |
Dismissed: Same as findings #2-3. grecn->grec_src at line 2936 is within the already-validated packet window.
Finding #5 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 2872 |
| Taint snippet | nsrcs = ntohs(grec->grec_nsrcs); |
| Tainted var | grec |
| Pointer deref | grec->grec_src line 2941 |
| Sink snippet | grec->grec_src, |
| Possibly guarded | no |
Dismissed: Same reasoning. grec->grec_src at line 2941 is safe.
Finding #6 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 2872 |
| Taint snippet | nsrcs = ntohs(grec->grec_nsrcs); |
| Tainted var | grec |
| Pointer deref | grec->grec_src line 2946 |
| Sink snippet | grec->grec_src, |
| Possibly guarded | no |
Dismissed: Same reasoning. grec->grec_src at line 2946 is safe.
Finding #7 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 2872 |
| Taint snippet | nsrcs = ntohs(grec->grec_nsrcs); |
| Tainted var | grec |
| Pointer deref | grec->grec_src line 2951 |
| Sink snippet | grec->grec_src, |
| Possibly guarded | no |
Dismissed: Same reasoning. grec->grec_src at line 2951 is safe.
Finding #8 — Category F — cross-function via br_multicast_isinc_allow() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 2872 |
| Taint snippet | nsrcs = ntohs(grec->grec_nsrcs); |
| Tainted var | nsrcs |
| Call site | line 2925 — passes nsrcs to br_multicast_isinc_allow() |
| Call snippet | changed = br_multicast_isinc_allow(brmctx, pg, h_addr, |
| Loop | for_loop line 2331 |
| Sink snippet | for (src_idx = 0; src_idx < nsrcs; src_idx++) { |
| Possibly guarded | yes (heuristic) |
Dismissed: nsrcs is passed to br_multicast_isinc_allow along with grec->grec_src as the source array. The ip_mc_may_pull check at line 2875-2876 already validated that nsrcs*4 bytes are accessible starting at grec->grec_src. The callee loop accesses exactly src_idx*addr_size offsets within this validated range. No counterexample possible.
br_ip6_multicast_mld2_report() — net/bridge/br_multicast.c FP confidence=high
The function has robust per-iteration bounds checking. For finding #1, the loop always terminates on malformed packets via -EINVAL from ipv6_mc_may_pull() or the nsrcs_offset check, making the unvalidated 'num' safe. For finding #2, nsrcs is used in struct_size(grec, grec_src, nsrcs) + ipv6_mc_may_pull() BEFORE the grec pointer is formed and before nsrcs is passed to isinc_allow, establishing exactly the bounds that the callee's loop uses. The caller also runs ipv6_mc_check_mld() as a pre-validation gate.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 2987 |
| Taint snippet | num = ntohs(mld2r->mld2r_ngrec); |
| Tainted var | num |
| Loop | for_loop line 2990 |
| Sink snippet | for (i = 0; i < num; i++) { |
| Possibly guarded | no |
Dismissed: The loop body contains per-iteration bounds checks: (1) lines 2996-2998 check nsrcs_offset+sizeof(__nsrcs) against packet length, (2) line 3008-3009 calls ipv6_mc_may_pull(skb, len+grec_len) which returns -EINVAL if data is unavailable. Since len grows by grec_len each iteration, if num is larger than actual record count, the loop exits early with -EINVAL. No counterexample can pass both guards with a truncated packet. False positive.
Finding #2 — Category F — cross-function via br_multicast_isinc_allow() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 3005 |
| Taint snippet | nsrcs = ntohs(*_nsrcs); |
| Tainted var | nsrcs |
| Call site | line 3061 — passes nsrcs to br_multicast_isinc_allow() |
| Call snippet | changed = br_multicast_isinc_allow(brmctx, pg, h_addr, |
| Loop | for_loop line 2331 |
| Sink snippet | for (src_idx = 0; src_idx < nsrcs; src_idx++) { |
| Possibly guarded | yes (heuristic) |
Dismissed: Before passing nsrcs to br_multicast_isinc_allow(), the code at line 3006-3009 computes grec_len = struct_size(grec, grec_src, nsrcs) and calls ipv6_mc_may_pull(skb, len + grec_len), which validates that nsrcs source entries (each sizeof(struct in6_addr)) fit in the skb. The callee's loop accesses srcs + (src_idx * addr_size) for src_idx < nsrcs — identical arithmetic. No counterexample exists: any nsrcs that passes ipv6_mc_may_pull already guarantees the array is in-bounds. False positive.
ebt_vlan_mt() — net/bridge/netfilter/ebt_vlan.c FP confidence=high
The function parses a VLAN TCI field from a network packet. The 'prio' field is derived by right-shifting TCI by 13 bits and masking with 0x7, yielding a value in [0,7], which trivially fits in an unsigned char. The scanner's own note specifies this pattern as a false positive. No bounds issue exists.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohs() line 60 |
| Taint snippet | prio = (TCI >> 13) & 0x7; |
| Tainted var | prio |
| Truncation | line 60: 16 → 8-bit u8 |
| Sink snippet | prio = (TCI >> 13) & 0x7; |
| Possibly guarded | no |
Dismissed: The expression '(TCI >> 13) & 0x7' limits the result to the range [0,7] regardless of the 16-bit TCI value. No counterexample exists: any possible TCI input produces a value that fits in unsigned char. The truncation is safe because the masking occurs on the RHS before assignment. This matches the scanner's own false-positive exception rule.
compat_do_replace() — net/bridge/netfilter/ebtables.c FP confidence=high
The function copies user-supplied tmp.entries_size from userspace and uses it both as the vmalloc() allocation size and the copy_from_user() size argument. Since both use the same value, the copy cannot overflow the allocated buffer. The vmalloc() failure path is checked before the copy. While tmp.entries_size is user-controlled and unvalidated for lower/upper bounds as a semantic matter, there is no memory safety vulnerability: the allocation and copy are paired. The finding is a false positive for OOB write.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_from_user() line 2323 |
| Taint snippet | if (copy_from_user( |
| Tainted var | tmp.entries_size |
| Unvalidated size | copy_from_user() arg 2 line 2323 — size tmp.entries_size |
| Sink snippet | if (copy_from_user( |
| Possibly guarded | no |
Dismissed: tmp.entries_size is user-controlled, but vmalloc(tmp.entries_size) is called first and its result is checked for NULL before copy_from_user uses the same size. The copy destination buffer is exactly sized to tmp.entries_size, so no OOB write is possible. Counterexample search: any value of tmp.entries_size that passes vmalloc (non-zero, not too large to allocate) will result in a correctly sized buffer — no concrete counterexample found. False positive.
compat_match_to_user() — net/bridge/netfilter/ebtables.c FP confidence=high
The flagged value 'strlen(match->name) + 1' is not user-supplied or server-supplied data. 'match' is a 'struct xt_match *' obtained from the kernel's internal match registry (m->u.match), where 'name' is a kernel-defined constant string embedded in the registered match descriptor. The destination buffer 'cm->u.name' is a fixed-size field (XT_EXTENSION_MAXNAMELEN / 29 bytes) in the user-space struct, and match names in the kernel are constrained to that same maximum length by the netfilter registration infrastructure. The size argument to copy_to_user() is therefore kernel-internally controlled and bounded, not server- or user-supplied.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 1659 |
| Taint snippet | if (copy_to_user(cm->u.name, match->name, strlen(match->name) + 1) || |
| Tainted var | strlen(match->name) + 1 |
| Unvalidated size | copy_to_user() arg 2 line 1659 — size strlen(match->name) + 1 |
| Sink snippet | if (copy_to_user(cm->u.name, match->name, strlen(match->name) + 1) || |
| Possibly guarded | no |
Dismissed: The taint source is misidentified: copy_to_user() is flagged as a taint source because it is used in a conditional, but the size argument 'strlen(match->name) + 1' derives from 'match->name', which is a kernel-registered constant string in struct xt_match, not user- or network-supplied data. All registered match names must fit within XT_EXTENSION_MAXNAMELEN (typically 29 bytes) as enforced by xt_register_match(). The destination cm->u.name has the same fixed-size bound. No counterexample can be constructed because the name length is bounded at registration time by the kernel itself. This is a false positive.
compat_target_to_user() — net/bridge/netfilter/ebtables.c FP confidence=high
The flagged copy_to_user() copies target->name, where 'target' is a struct xt_target — a kernel-internal object registered via xt_register_target(). The 'name' field is a fixed-size char array (XT_EXTENSION_MAXNAMELEN = 29 bytes) filled at module registration time with a string literal or compile-time constant. It is not server-supplied or user-controlled. The destination cm->u.name is also a fixed-size field of the same size in compat_ebt_entry_mwt. The scanner is confused because the taint source is misidentified — strlen(target->name)+1 is computed from a kernel-internal string, not from any network packet or user-supplied data.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 1691 |
| Taint snippet | if (copy_to_user(cm->u.name, target->name, strlen(target->name) + 1) || |
| Tainted var | strlen(target->name) + 1 |
| Unvalidated size | copy_to_user() arg 2 line 1691 — size strlen(target->name) + 1 |
| Sink snippet | if (copy_to_user(cm->u.name, target->name, strlen(target->name) + 1) || |
| Possibly guarded | no |
Dismissed: target->name is a kernel-internal field in struct xt_target, populated at module registration with a compile-time string constant (XT_EXTENSION_MAXNAMELEN bytes max). It is not server-supplied or user-controlled. The copy_to_user() destination cm->u.name is a fixed-size array of the same length. No counterexample can be constructed because the value is not attacker-controlled. The scanner incorrectly treats the output of strlen() on a kernel-registered string as tainted. This is a false positive.
ebt_obj_to_user() — net/bridge/netfilter/ebtables.c FP confidence=high
EBT_EXTENSION_MAXNAMELEN is a compile-time constant defined in the kernel headers (typically 32 bytes). The scanner incorrectly flagged it as user-controlled because it appears as an argument to copy_to_user(), but it is not derived from any user-supplied or server-supplied data. The source buffer 'name' is a local stack array of exactly EBT_EXTENSION_MAXNAMELEN bytes, initialized to zero and filled via strscpy(). Copying EBT_EXTENSION_MAXNAMELEN bytes from a buffer of exactly that size is safe. The taint trace from copy_to_user() returning a value is a scanner artifact — the return value of copy_to_user() is a boolean success/failure indicator, not a size or buffer pointer, and is only used as part of the error-check OR chain.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 1458 |
| Taint snippet | if (copy_to_user(um, name, EBT_EXTENSION_MAXNAMELEN) || |
| Tainted var | EBT_EXTENSION_MAXNAMELEN |
| Unvalidated size | copy_to_user() arg 2 line 1458 — size EBT_EXTENSION_MAXNAMELEN |
| Sink snippet | if (copy_to_user(um, name, EBT_EXTENSION_MAXNAMELEN) || |
| Possibly guarded | no |
Dismissed: EBT_EXTENSION_MAXNAMELEN is a kernel-internal compile-time constant, not a user-supplied or server-supplied value. The scanner misidentified it as tainted, likely because the taint propagation tracked the copy_to_user() call itself as a taint source and then re-encountered the constant in the same expression. The local 'name' array is exactly EBT_EXTENSION_MAXNAMELEN bytes, so copying that many bytes from it is correct and safe. No counterexample can be constructed because no runtime value is involved — the size is fixed at compile time. This is a false positive.
encrypt_authorizer() — net/ceph/auth_x.c FP confidence=high
The le32_to_cpu() at line 357 reads msg_a->ticket_blob.blob_len, which was written by the kernel itself via cpu_to_le32(ticket_blob_len) in ceph_x_build_authorizer() before encrypt_authorizer() is called. The buffer au->buf is allocated with maxlen = sizeof(*msg_a) + ticket_blob_len + ceph_x_encrypt_buflen(&au->session_key, sizeof(*msg_b)), which precisely accounts for all offsets. In the second call site (ceph_x_add_authorizer_challenge), the authorizer was previously built by ceph_x_build_authorizer() so the same invariant holds. The blob_len value is locally controlled, not server-supplied, making all six findings false positives.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 357 |
| Taint snippet | p = (void *)(msg_a + 1) + le32_to_cpu(msg_a->ticket_blob.blob_len); |
| Tainted var | msg_b |
| Pointer deref | msg_b->struct_v line 361 |
| Sink snippet | msg_b->struct_v = 2; |
| Possibly guarded | no |
Dismissed: blob_len is written locally by the kernel as cpu_to_le32(ticket_blob_len) and the buffer is allocated to exactly fit sizeof(*msg_a)+ticket_blob_len+ceph_x_encrypt_buflen(...). No counterexample exists: any value that could be placed in blob_len must have been placed there by the kernel and matches the allocation. msg_b->struct_v write is within bounds.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 357 |
| Taint snippet | p = (void *)(msg_a + 1) + le32_to_cpu(msg_a->ticket_blob.blob_len); |
| Tainted var | msg_b |
| Pointer deref | msg_b->nonce line 362 |
| Sink snippet | msg_b->nonce = cpu_to_le64(au->nonce); |
| Possibly guarded | no |
Dismissed: Same reasoning as finding #1. msg_b->nonce write is within the locally-allocated buffer whose size accounts for all offsets.
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 357 |
| Taint snippet | p = (void *)(msg_a + 1) + le32_to_cpu(msg_a->ticket_blob.blob_len); |
| Tainted var | msg_b |
| Pointer deref | msg_b->have_challenge line 364 |
| Sink snippet | msg_b->have_challenge = 1; |
| Possibly guarded | no |
Dismissed: Same reasoning as finding #1. msg_b->have_challenge write is within bounds.
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 357 |
| Taint snippet | p = (void *)(msg_a + 1) + le32_to_cpu(msg_a->ticket_blob.blob_len); |
| Tainted var | msg_b |
| Pointer deref | msg_b->server_challenge_plus_one line 365 |
| Sink snippet | msg_b->server_challenge_plus_one = |
| Possibly guarded | no |
Dismissed: Same reasoning as finding #1. msg_b->server_challenge_plus_one write is 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 357 |
| Taint snippet | p = (void *)(msg_a + 1) + le32_to_cpu(msg_a->ticket_blob.blob_len); |
| Tainted var | msg_b |
| Pointer deref | msg_b->have_challenge line 368 |
| Sink snippet | msg_b->have_challenge = 0; |
| Possibly guarded | no |
Dismissed: Same reasoning as finding #1. The else-branch msg_b->have_challenge=0 write is within bounds.
Finding #6 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 357 |
| Taint snippet | p = (void *)(msg_a + 1) + le32_to_cpu(msg_a->ticket_blob.blob_len); |
| Tainted var | msg_b |
| Pointer deref | msg_b->server_challenge_plus_one line 369 |
| Sink snippet | msg_b->server_challenge_plus_one = 0; |
| Possibly guarded | no |
Dismissed: Same reasoning as finding #1. The else-branch msg_b->server_challenge_plus_one=0 write is within bounds.
ceph_alloc_middle() — net/ceph/messenger.c VALIDATE confidence=medium
The middle_len value is read from a network-received message header (msg->hdr.middle_len), making it genuinely server-supplied. The call chain in ceph_con_in_msg_alloc() checks that middle_len is non-zero before calling ceph_alloc_middle(), and ceph_alloc_middle() itself BUG_ONs if middle_len is zero. However, there is no upper-bound check on middle_len before it is passed to kvmalloc(). A server could supply an arbitrarily large middle_len value (e.g., UINT32_MAX or near it), which would cause kvmalloc() to fail (returning NULL) rather than actually allocating that much memory, so in practice this is handled gracefully by the NULL check at line 2046. However, if kvmalloc() were to succeed with an absurdly large value, the buffer would be undersized relative to the actual data expected. The key question is whether there is a protocol-level or earlier validation that bounds middle_len to a sane maximum before this code is reached — that context is not visible here.
Finding #1 — Category B — cross-function via ceph_buffer_new() — BUG undersized_alloc
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le32_to_cpu() line 2038 |
| Taint snippet | int middle_len = le32_to_cpu(msg->hdr.middle_len); |
| Tainted var | middle_len |
| Call site | line 2045 — passes middle_len to ceph_buffer_new() |
| Call snippet | msg->middle = ceph_buffer_new(middle_len, GFP_NOFS); |
| Sink (in callee) | kvmalloc() line 20 (arg 0, role=size) |
| Sink snippet | b->vec.iov_base = kvmalloc(len, gfp); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: With a very large middle_len, kvmalloc() will likely fail and return NULL, causing -ENOMEM and connection failure — benign in practice. However, if an attacker can supply a middle_len that is large enough to pass any unrelated protocol checks but small enough to succeed in allocation yet smaller than the actual data being received, subsequent data writes into msg->middle could write beyond the allocated buffer, causing heap corruption (KASAN: slab-out-of-bounds or memory corruption leading to kernel panic).
Fix: Add an upper-bound check on middle_len before calling ceph_alloc_middle() or inside it. For example, in ceph_con_in_msg_alloc() or ceph_alloc_middle(), validate: if (middle_len > CEPH_MSG_MAX_MIDDLE_LEN) return -EIO; where CEPH_MSG_MAX_MIDDLE_LEN is a reasonable protocol-defined constant (e.g., 16MB or whatever the Ceph protocol specifies as the maximum middle section size).
CVE pattern: Server-supplied length field used in allocation without upper-bound validation — similar in class to CVE-2020-25212 and other network protocol allocation size issues
alloc_msg_with_page_vector() — net/ceph/osd_client.c BUG confidence=high
alloc_msg_with_page_vector() receives a ceph_msg_header from the server (network packet) and directly uses server-supplied front_len and data_len without any upper-bound validation before passing them to allocation/page-vector functions. The hdr pointer is a received network buffer (this is the alloc_msg callback for incoming messages), so both fields are genuinely server-supplied. There is no cap on front_len before kvmalloc() or on data_len before the page allocation loop. A malicious server could send arbitrarily large values causing excessive memory allocation or OOM conditions.
Finding #1 — Category B — cross-function via ceph_msg_new2() — BUG undersized_alloc
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le32_to_cpu() line 5480 |
| Taint snippet | u32 front_len = le32_to_cpu(hdr->front_len); |
| Tainted var | front_len |
| Call site | line 5483 — passes front_len to ceph_msg_new2() |
| Call snippet | m = ceph_msg_new2(type, front_len, 1, GFP_NOIO, false); |
| Sink (in callee) | kvmalloc() line 1984 (arg 0, role=size) |
| Sink snippet | m->front.iov_base = kvmalloc(front_len, flags); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: A malicious server sending an extremely large front_len (up to 4GB for u32) would cause kvmalloc() to attempt a huge allocation, likely failing with ENOMEM or causing system memory pressure/OOM kill. No panic, but denial-of-service via memory exhaustion.
Fix: Add an upper-bound check on front_len before calling ceph_msg_new2(): e.g., if (front_len > CEPH_MSG_MAX_FRONT_LEN) return NULL; where CEPH_MSG_MAX_FRONT_LEN is a reasonable protocol-defined maximum (e.g., 64KB or whatever the OSD protocol specifies).
CVE pattern: Server-supplied length field used directly in kernel allocation without upper-bound validation — similar pattern to CVE-2021-22543 style resource exhaustion via crafted network messages.
Finding #2 — Category F — cross-function via ceph_alloc_page_vector() — BUG undersized_alloc
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le32_to_cpu() line 5481 |
| Taint snippet | u32 data_len = le32_to_cpu(hdr->data_len); |
| Tainted var | data_len |
| Call site | line 5490 — passes data_len to ceph_alloc_page_vector() |
| Call snippet | pages = ceph_alloc_page_vector(calc_pages_for(0, data_len), |
| Loop | for_loop line 47 |
| Sink snippet | for (i = 0; i < num_pages; i++) { |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: A malicious server sending a very large data_len would cause calc_pages_for(0, data_len) to return a huge page count, causing ceph_alloc_page_vector() to attempt to allocate a huge array of page pointers and then allocate that many individual pages, leading to massive memory consumption or OOM. With data_len near UINT_MAX, calc_pages_for could overflow producing a smaller-than-expected page count while data_len remains large, leading to an undersized page vector used with ceph_msg_data_add_pages().
Fix: Add an upper-bound check on data_len before using it: e.g., if (data_len > CEPH_MSG_MAX_DATA_LEN) { ceph_msg_put(m); return NULL; }. Also check for integer overflow in calc_pages_for(0, data_len) — ensure the page count does not wrap. A reasonable maximum should be enforced per protocol constraints.
CVE pattern: Server-supplied data length used to derive page allocation count without upper bound — denial-of-service via memory exhaustion, similar to resource exhaustion bugs in network filesystem clients.
get_reply() — net/ceph/osd_client.c FP confidence=high
The function properly validates server-supplied values: front_len is checked against preallocated size (line 5442), data_len is checked against data_length (line 5455). The taint propagation for finding #1 is incorrect — 'req' is retrieved from a kernel-internal RB-tree using the server-supplied 'tid' as a lookup key, but the retrieved object itself is kernel-allocated and kernel-controlled. The server cannot influence req->r_num_ops; this field was set when the kernel built the OSD request.
Finding #1 — Category F — cross-function via sparse_data_requested() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le64_to_cpu() line 5420 |
| Taint snippet | u64 tid = le64_to_cpu(hdr->tid); |
| Tainted var | req |
| Call site | line 5454 — passes req to sparse_data_requested() |
| Call snippet | srlen = sparse_data_requested(req); |
| Loop | for_loop line 5395 |
| Sink snippet | for (i = 0; i < req->r_num_ops; ++i) { |
| Possibly guarded | yes (heuristic) |
Dismissed: The scanner incorrectly propagates taint from server-supplied 'tid' through the lookup_request() call to the returned 'req' pointer. However, req is a kernel-allocated ceph_osd_request struct created when the kernel issued the OSD request; the server only supplies the tid used as a lookup key. req->r_num_ops is set by kernel code and cannot be influenced by server data. No counterexample exists because the server has no path to write into req->r_num_ops. This is a classic false positive: using a tainted value as an index/key into a kernel data structure does not taint the retrieved kernel object.
handle_reply() — net/ceph/osd_client.c FP confidence=high
The function has strong validation: decode_MOSDOpReply() validates all decoded fields with bounds checks, m.num_ops is checked to equal the kernel-side req->r_num_ops before the loop, and req itself is a kernel-allocated structure looked up by tid (not populated from the server). The loop bound req->r_num_ops is kernel-controlled, not server-controlled. The scanner incorrectly propagated taint from le64_to_cpu(msg->hdr.tid) through lookup_request() to req->r_num_ops.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le64_to_cpu() line 3754 |
| Taint snippet | u64 tid = le64_to_cpu(msg->hdr.tid); |
| Tainted var | req |
| Loop | for_loop line 3844 |
| Sink snippet | for (i = 0; i < req->r_num_ops; i++) { |
| Possibly guarded | yes (heuristic) |
Dismissed: The loop bound req->r_num_ops is from a kernel-allocated ceph_osd_request structure, not from the server message. The server-supplied m.num_ops is separately validated to equal req->r_num_ops at line 3839 before the loop. Array accesses m.rval[i] and m.outdata_len[i] are bounded by decode_MOSDOpReply()'s postcondition which guarantees num_ops <= ARRAY_SIZE(m->outdata_len). No counterexample can be constructed — all paths are protected. The taint propagation through lookup_request() is a scanner false positive.
osd_sparse_read() — net/ceph/osd_client.c FP confidence=high
The code uses a state machine. `count` is server-supplied but is bounded by a successful kmalloc_objs() allocation of exactly `count` elements in the EXTENTS state. convert_extent_map() traverses the same buffer with the same count immediately before the flagged loop, constituting a structurally equivalent prior full traversal. No counterexample exists where the loop could go OOB.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le32_to_cpu() line 5826 |
| Taint snippet | count = le32_to_cpu((__force __le32)sr->sr_count); |
| Tainted var | count |
| Loop | for_loop line 5859 |
| Sink snippet | for (i = 0; i < count; i++) |
| Possibly guarded | yes (heuristic) |
Dismissed: The allocation at line 5834 allocates exactly `count` elements (guarded by the count > sr->sr_ext_len check). sr->sr_count is set to count at line 5827, so on subsequent state machine re-entry, count (line 5808) equals the allocated size. convert_extent_map() at line 5851 performs a structurally identical traversal over [0, sr->sr_count) immediately before the flagged loop, which would fault first if the count were invalid. No counterexample can be constructed where the loop at 5859 exceeds the allocated array bounds.
prep_next_sparse_read() — net/ceph/osd_client.c FP confidence=high
The taint from le64_to_cpu(con->in_msg->hdr.tid) is used only to look up a kernel-allocated request object via lookup_request(). The returned req->r_num_ops and req->r_ops[] are locally-constructed kernel fields, not server-supplied values. The loop bound is the kernel-internal r_num_ops, which accurately reflects the allocated size of r_ops[]. The loop condition itself (++o->o_sparse_op_idx < req->r_num_ops) is a proper bounds check.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le64_to_cpu() line 5725 |
| Taint snippet | req = lookup_request(&o->o_requests, le64_to_cpu(con->in_msg->hdr.tid)); |
| Tainted var | req |
| Loop | while_loop line 5757 |
| Sink snippet | while (++o->o_sparse_op_idx < req->r_num_ops) { |
| Possibly guarded | no |
Dismissed: The server-supplied TID is used only as a lookup key into a radix/RB tree of locally-allocated ceph_osd_request objects. The req->r_num_ops field controlling the loop bound is set by the kernel at request construction time, not populated from any server response. No counterexample can be constructed because r_num_ops == allocated size of r_ops[], and the loop condition enforces this bound directly.
cmsghdr_from_user_compat_to_kern() — net/compat.c FP confidence=high
The function has a careful two-pass design. In the second pass, cmsg is copied atomically into a kernel-local struct, CMSG_COMPAT_OK validates cmsg.cmsg_len fits within the user message, and the line-189 guard verifies the output buffer has room for CMSG_ALIGN(tmp) bytes where tmp already includes sizeof(struct cmsghdr) plus the data length. This means CMSG_DATA(kcmsg) has sufficient space for (cmsg.cmsg_len - sizeof(*ucmsg)) bytes. No counterexample can be constructed that passes all guards and causes OOB.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_from_user() line 195 |
| Taint snippet | if (copy_from_user(CMSG_DATA(kcmsg), |
| Tainted var | (cmsg.cmsg_len - sizeof(*ucmsg)) |
| Unvalidated size | copy_from_user() arg 2 line 195 — size (cmsg.cmsg_len - sizeof(*ucmsg)) |
| Sink snippet | if (copy_from_user(CMSG_DATA(kcmsg), |
| Possibly guarded | no |
Dismissed: cmsg is read atomically into a kernel local struct (no TOCTOU). CMSG_COMPAT_OK at line 186 validates cmsg.cmsg_len >= sizeof(*ucmsg) and fits within the user message. The guard at line 189 checks that CMSG_ALIGN(tmp) fits in remaining kernel buffer where tmp = (cmsg.cmsg_len - sizeof(*ucmsg)) + sizeof(struct cmsghdr). Since CMSG_ALIGN(tmp) >= sizeof(struct cmsghdr) + (cmsg.cmsg_len - sizeof(*ucmsg)), the CMSG_DATA(kcmsg) area (offset sizeof(struct cmsghdr) into kcmsg) has sufficient room for the copy. Could not construct a counterexample that bypasses all guards.
put_cmsg_compat() — net/compat.c FP confidence=high
The cmlen value in put_cmsg_compat() is derived from kernel-internal len parameter and capped by kmsg->msg_controllen. The check at line 234 ensures msg_controllen >= sizeof(*cm), which after the cap at line 264 ensures cmlen >= sizeof(struct compat_cmsghdr), preventing underflow. The len parameter comes from the kernel socket subsystem, not from user/server input. The scanner incorrectly flagged copy_to_user() as a taint source and traced the arithmetic through an already-bounded value.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 272 |
| Taint snippet | if (copy_to_user(CMSG_COMPAT_DATA(cm), data, cmlen - sizeof(struct compat_cmsghdr))) |
| Tainted var | cmlen - sizeof(struct compat_cmsghdr) |
| Unvalidated size | copy_to_user() arg 2 line 272 — size cmlen - sizeof(struct compat_cmsghdr) |
| Sink snippet | if (copy_to_user(CMSG_COMPAT_DATA(cm), data, cmlen - sizeof(struct compat_cmsghdr))) |
| Possibly guarded | no |
Dismissed: cmlen is set to CMSG_COMPAT_LEN(len) (kernel-internal) and capped to kmsg->msg_controllen. The early guard at line 234 ensures msg_controllen >= sizeof(*cm); after the cap at line 264, cmlen >= sizeof(struct compat_cmsghdr) is guaranteed, so the subtraction cannot underflow. No counterexample can be constructed: to make cmlen < sizeof(compat_cmsghdr) you would need msg_controllen < sizeof(*cm), but that is caught at line 234-237 with early return. The taint source being copy_to_user() itself is a scanner artifact — copy_to_user is a sink, not a source of tainted data.
netdev_cmd_to_name() — net/core/dev.c FP confidence=high
The flagged access `ptype_base[ntohs(type) & PTYPE_HASH_MASK]` is a classic false positive. The value used as the array subscript is `ntohs(type) & PTYPE_HASH_MASK`. The bitwise AND with PTYPE_HASH_MASK is the bounds check: it masks the result to a fixed number of bits, guaranteeing the index is always within [0, PTYPE_HASH_MASK], which is exactly the size of the ptype_base array. No counterexample can be constructed — there is no value of ntohs(type) that, after being ANDed with PTYPE_HASH_MASK, could produce an out-of-bounds index.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 6157 |
| Taint snippet | &ptype_base[ntohs(type) & |
| Tainted var | ntohs(type) &
PTYPE_HASH_MASK |
| Subscript | [] line 6157 |
| Sink snippet | &ptype_base[ntohs(type) & |
| Possibly guarded | no |
Dismissed: The index `ntohs(type) & PTYPE_HASH_MASK` is inherently bounded by the bitwise AND. PTYPE_HASH_MASK is defined as (PTYPE_HASH_SIZE - 1) where PTYPE_HASH_SIZE is the length of ptype_base, so the mask operation is a perfect modulo that can never produce an out-of-bounds index. No counterexample exists: for any possible uint16_t value from ntohs(), applying & PTYPE_HASH_MASK yields a value in [0, PTYPE_HASH_MASK] ⊆ valid indices. This is a standard and correct bitmask-based bounds enforcement pattern. The static analyser flagged it purely because ntohs() returns a network-supplied value, but the subsequent masking makes it safe.
ptype_head() — net/core/dev.c FP confidence=high
The `ntohs(pt->type)` value is masked with `PTYPE_HASH_MASK` before being used as an array subscript. The bitwise AND operation guarantees the result is strictly bounded by `PTYPE_HASH_MASK`, which is defined as a power-of-two-minus-one mask (e.g., 0xF or 0x1F) corresponding exactly to the size of the `ptype_base` array. No counterexample can be constructed: for any 16-bit value of `pt->type`, `ntohs(pt->type) & PTYPE_HASH_MASK` is always in the range `[0, PTYPE_HASH_MASK]`, which is always a valid index into `ptype_base`. This is a textbook safe hash-table index computation. The scanner flagged it because `ntohs()` is a taint source, but the mask makes it safe regardless of the input value.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 608 |
| Taint snippet | &ptype_base[ntohs(pt->type) & PTYPE_HASH_MASK]; |
| Tainted var | ntohs(pt->type) & PTYPE_HASH_MASK |
| Subscript | [] line 608 |
| Sink snippet | &ptype_base[ntohs(pt->type) & PTYPE_HASH_MASK]; |
| Possibly guarded | no |
Dismissed: The expression `ntohs(pt->type) & PTYPE_HASH_MASK` applies a bitmask before indexing. PTYPE_HASH_MASK is defined as (PTYPE_HASH_SIZE - 1) where PTYPE_HASH_SIZE is the compile-time size of the ptype_base array. For any possible 16-bit input value, the masked result is always in [0, PTYPE_HASH_MASK], which is always a valid array index. No counterexample exists — there is no input value that passes the mask and still produces an out-of-bounds index. This is a false positive.
__get_filter() — net/core/filter.c FP confidence=high
The function validates fprog->len via bpf_check_basics_ok() before allocation and copy. The allocation bpf_prog_size(fprog->len) sizes prog->insns to hold exactly fprog->len instructions, so fsize = bpf_classic_proglen(fprog) = fprog->len * sizeof(struct sock_filter) fits within the allocated buffer. The bounds check is both present and sufficient.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_from_user() line 1519 |
| Taint snippet | if (copy_from_user(prog->insns, fprog->filter, fsize)) { |
| Tainted var | fsize |
| Unvalidated size | copy_from_user() arg 2 line 1519 — size fsize |
| Sink snippet | if (copy_from_user(prog->insns, fprog->filter, fsize)) { |
| Possibly guarded | no |
Dismissed: bpf_check_basics_ok() validates fprog->len is in [1, BPF_MAXINSNS] before the allocation and copy. bpf_prog_alloc(bpf_prog_size(fprog->len)) allocates exactly enough space for fprog->len instructions, which matches fsize = fprog->len * sizeof(struct sock_filter). No counterexample could be constructed: any len passing the basics check produces an allocation large enough for fsize bytes. The scanner flagged this because it did not trace through bpf_check_basics_ok() and the size-capacity relationship between bpf_prog_size() and bpf_classic_proglen().
bpf_prog_create_from_user() — net/core/filter.c FP confidence=high
bpf_check_basics_ok() at line 1434 validates fprog->len before any use of fsize. It ensures len is in [1, BPF_MAXINSNS], preventing integer overflow in fsize computation. Both bpf_prog_alloc (via bpf_prog_size) and copy_from_user (via bpf_classic_proglen) use len*8 bytes, so the destination buffer is always exactly large enough for the copy. No counterexample exists where the copy exceeds the allocation.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_from_user() line 1441 |
| Taint snippet | if (copy_from_user(fp->insns, fprog->filter, fsize)) { |
| Tainted var | fsize |
| Unvalidated size | copy_from_user() arg 2 line 1441 — size fsize |
| Sink snippet | if (copy_from_user(fp->insns, fprog->filter, fsize)) { |
| Possibly guarded | no |
Dismissed: bpf_check_basics_ok() validates fprog->len in [1, BPF_MAXINSNS] before fsize is used. fsize = fprog->len * sizeof(struct sock_filter) and the allocation is bpf_prog_size(fprog->len) = sizeof(struct bpf_prog) + fprog->len * sizeof(struct bpf_insn). Since sizeof(struct sock_filter) == sizeof(struct bpf_insn) == 8 bytes on all supported architectures, the copy destination (fp->insns) is always exactly large enough. No counterexample can be constructed — the guard is tight and sufficient.
ptype_seq_next() — net/core/net-procfs.c FP confidence=high
The scanner incorrectly treats ntohs(pt->type) as server-supplied data. Here pt is a kernel-internal struct packet_type registered via dev_add_pack() or similar — its type field is set by the kernel, not received from the network. Furthermore, the & PTYPE_HASH_MASK operation mathematically constrains hash to [0, PTYPE_HASH_SIZE-1], making any out-of-bounds access impossible. The while loop's ++hash check (line 279) guards the line 281 access. Both findings are false positives.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 276 |
| Taint snippet | hash = ntohs(pt->type) & PTYPE_HASH_MASK; |
| Tainted var | hash |
| Subscript | [] line 278 |
| Sink snippet | while (nxt == &ptype_base[hash]) { |
| Possibly guarded | no |
Dismissed: pt->type is a kernel-internal protocol identifier stored in network byte order in struct packet_type. The & PTYPE_HASH_MASK bitmask guarantees hash is in [0, PTYPE_HASH_SIZE-1]. No counterexample exists: any 16-bit value ANDed with PTYPE_HASH_MASK (= PTYPE_HASH_SIZE-1) is strictly less than PTYPE_HASH_SIZE. False positive.
Finding #2 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 276 |
| Taint snippet | hash = ntohs(pt->type) & PTYPE_HASH_MASK; |
| Tainted var | hash |
| Subscript | [] line 281 |
| Sink snippet | nxt = READ_ONCE(ptype_base[hash].next); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same analysis as finding #1 for the initial value of hash. Additionally, line 279 explicitly checks `if (++hash >= PTYPE_HASH_SIZE) return NULL` before reaching line 281, providing a second layer of protection for the incremented value. No counterexample exists. False positive.
pf() — net/core/pktgen.c FP confidence=high
All four findings are false positives. Findings #1-#3 are based on incorrect taint attribution: the scanner confused separate code paths (IPv4 destination address computation using ntohl() vs. IMIX packet size selection using get_random_u32_below()). The value 't' at line 2654 comes from get_random_u32_below(IMIX_PRECISION), a kernel RNG bounded call, not from ntohl(). The entry_index is a kernel-internal value written by fill_imix_distribution(). Finding #4 is a false positive because 'max' is computed as min(count, sizeof(data)-1), explicitly bounded to fit within the destination buffer.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohl() line 2654 |
| Taint snippet | __u8 entry_index = pkt_dev->imix_distribution[t]; |
| Tainted var | entry_index |
| Truncation | line 2654: 32 → 8-bit __u8 |
| Sink snippet | __u8 entry_index = pkt_dev->imix_distribution[t]; |
| Possibly guarded | no |
Dismissed: The scanner incorrectly attributes taint from ntohl() at line 2588 (IPv4 dst address path) to line 2654 (IMIX path). These are separate else-if branches. The value 't' at line 2654 is from get_random_u32_below(IMIX_PRECISION), which is kernel-internal and bounded. entry_index is a __u8 loaded from imix_distribution[], which is filled by kernel-internal fill_imix_distribution(). No server-supplied data involved.
Finding #2 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohl() line 2588 |
| Taint snippet | imn = ntohl(pkt_dev->daddr_min); |
| Tainted var | t |
| Subscript | [] line 2654 |
| Sink snippet | __u8 entry_index = pkt_dev->imix_distribution[t]; |
| Possibly guarded | no |
Dismissed: Same misattribution as finding #1. The taint source ntohl() at line 2588 is in the IPv4 destination address computation branch, which is mutually exclusive with the IMIX branch at line 2651. The subscript 't' at line 2654 is bounded by IMIX_PRECISION via get_random_u32_below().
Finding #3 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohl() line 2588 |
| Taint snippet | imn = ntohl(pkt_dev->daddr_min); |
| Tainted var | entry_index |
| Subscript | [] line 2656 |
| Sink snippet | entry = &pkt_dev->imix_entries[entry_index]; |
| Possibly guarded | no |
Dismissed: Same misattribution. entry_index is loaded from imix_distribution[] which is a kernel-internal array filled by fill_imix_distribution(). While there could theoretically be a kernel-internal logic bug in fill_imix_distribution() (j could reach n_imix_entries), this is not a server-supplied/user-supplied taint issue as the scanner claims.
Finding #4 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_from_user() line 533 |
| Taint snippet | if (copy_from_user(data, buf, max)) |
| Tainted var | max |
| Unvalidated size | copy_from_user() arg 2 line 533 — size max |
| Sink snippet | if (copy_from_user(data, buf, max)) |
| Possibly guarded | no |
Dismissed: max = min(count, sizeof(data) - 1) explicitly caps the copy length to 127 bytes, which fits within data[128]. The copy_from_user call is safe. The size is not user-controlled beyond the min() cap.
skb_checksum_setup_ipv6() — net/core/skbuff.c FP confidence=high
The function uses per-iteration bounds validation via skb_maybe_pull_tail() before any header pointer dereference. Even though 'len' is derived from a server-supplied payload_len without explicit range validation against the actual skb length, each loop iteration calls skb_maybe_pull_tail() which verifies that the required bytes are actually present in the skb before proceeding. If the packet is too short or payload_len is inflated, skb_maybe_pull_tail returns a negative error, triggering 'goto out'. The MAX_IPV6_HDR_LEN cap (576 bytes) further limits pull operations. No OOB access is possible because no pointer dereference occurs without a successful skb_maybe_pull_tail check immediately preceding it.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 5955 |
| Taint snippet | len = sizeof(struct ipv6hdr) + ntohs(ipv6_hdr(skb)->payload_len); |
| Tainted var | len |
| Loop | while_loop line 5956 |
| Sink snippet | while (off <= len && !done) { |
| Possibly guarded | no |
Dismissed: Attempted counterexample: payload_len=65535 makes len=65575, but the loop body calls skb_maybe_pull_tail(skb, off+sizeof(header), MAX_IPV6_HDR_LEN) before dereferencing any header pointer. If off+sizeof(header) exceeds available skb data, this returns -EPROTO or similar negative value, causing 'goto out' before any OOB access. No counterexample found — the per-iteration validation is sufficient protection regardless of the value of 'len'.
skb_mpls_dec_ttl() — net/core/skbuff.c FP confidence=high
The function correctly validates that the skb contains enough data via pskb_may_pull() before accessing the MPLS header. The TTL field is extracted with a bitmask (MPLS_LS_TTL_MASK = 0xFF) that limits the result to exactly 8 bits before assignment to u8 ttl, so no truncation occurs. The scanner flagged a 32→8 bit narrowing but missed that the masking makes the narrowing safe. The subsequent decrement-and-zero check also prevents forwarding with a zero TTL.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | be32_to_cpu() line 6737 |
| Taint snippet | ttl = (lse & MPLS_LS_TTL_MASK) >> MPLS_LS_TTL_SHIFT; |
| Tainted var | ttl |
| Truncation | line 6737: 32 → 8-bit u8 |
| Sink snippet | ttl = (lse & MPLS_LS_TTL_MASK) >> MPLS_LS_TTL_SHIFT; |
| Possibly guarded | no |
Dismissed: The RHS expression applies MPLS_LS_TTL_MASK (0x000000FF) before the right-shift by MPLS_LS_TTL_SHIFT (0), so the result is always in [0, 255] — exactly the range of u8. No counterexample exists where the 32-bit intermediate value exceeds 255 after masking. The scanner's own guidance states this is a false positive when masking limits the value to the destination width before assignment.
sock_ioctl_inout() — net/core/sock.c FP confidence=high
sock_ioctl_inout() is a kernel-internal helper; 'size' is passed by the kernel caller, not read from user space. The scanner incorrectly treats 'size' as user-controlled because it appears in copy_from_user(), but 'size' is a kernel-supplied parameter whose value is determined by the calling context (e.g., sizeof(struct ifreq) or similar compile-time constant passed by sock_do_ioctl or equivalent). The actual user-supplied data is 'arg' (the user pointer), not 'size'. Both copy_from_user() and copy_to_user() use the same kernel-controlled 'size' for 'karg', which was also allocated by the kernel caller to exactly that size. No user-controlled size is involved.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_from_user() line 4495 |
| Taint snippet | if (copy_from_user(karg, arg, size)) |
| Tainted var | size |
| Unvalidated size | copy_from_user() arg 2 line 4495 — size size |
| Sink snippet | if (copy_from_user(karg, arg, size)) |
| Possibly guarded | no |
Dismissed: The 'size' parameter is a kernel-internal value passed by the kernel caller (not derived from user input). The scanner misidentifies copy_from_user() as a taint source for 'size', but 'size' is the third argument to copy_from_user(), not a value read from user space. The taint source trace is erroneous: copy_from_user() taints 'karg' (the destination buffer), not 'size'. This is a false positive due to the scanner confusing the size parameter of copy_from_user with user-supplied data.
Finding #2 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 4502 |
| Taint snippet | if (copy_to_user(arg, karg, size)) |
| Tainted var | size |
| Unvalidated size | copy_to_user() arg 2 line 4502 — size size |
| Sink snippet | if (copy_to_user(arg, karg, size)) |
| Possibly guarded | no |
Dismissed: Same reasoning as finding #1. 'size' in copy_to_user() is kernel-controlled, matching the size used for 'karg' allocation by the caller. The scanner's taint propagation is incorrect here — copy_to_user() does not introduce 'size' as a tainted value, and 'size' was never read from user space in this function. False positive.
ar9331_tag_rcv() — net/dsa/tag_ar9331.c FP confidence=high
Both findings are false positives. FIELD_GET() applies a bitmask (the _MASK constant) to extract a sub-field from the 16-bit hdr value before assignment. The extracted bits are always narrower than or equal to 8 bits, so no truncation of meaningful bits occurs. For 'ver': AR9331_HDR_VERSION_MASK extracts only the version field bits, and the subsequent check against AR9331_HDR_VERSION ensures only valid values proceed. For 'port': AR9331_HDR_PORT_NUM_MASK extracts only the port number bits (AR9331 switch has a small number of ports, far fewer than 256), and dsa_conduit_find_user() returns NULL for invalid ports, causing safe early exit.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 61 |
| Taint snippet | ver = FIELD_GET(AR9331_HDR_VERSION_MASK, hdr); |
| Tainted var | ver |
| Truncation | line 61: 16 → 8-bit u8 |
| Sink snippet | ver = FIELD_GET(AR9331_HDR_VERSION_MASK, hdr); |
| Possibly guarded | no |
Dismissed: FIELD_GET(AR9331_HDR_VERSION_MASK, hdr) applies the mask before assignment, so the 8-bit 'ver' variable holds only the masked bits. The mask limits the value to fit in u8. Additionally, the subsequent check 'ver != AR9331_HDR_VERSION' drops any packet with an unexpected version value. No counterexample can be constructed: any value that passes FIELD_GET masking is bounded by the mask width, and the version equality check further constrains it. False positive.
Finding #2 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 79 |
| Taint snippet | port = FIELD_GET(AR9331_HDR_PORT_NUM_MASK, hdr); |
| Tainted var | port |
| Truncation | line 79: 16 → 8-bit u8 |
| Sink snippet | port = FIELD_GET(AR9331_HDR_PORT_NUM_MASK, hdr); |
| Possibly guarded | no |
Dismissed: FIELD_GET(AR9331_HDR_PORT_NUM_MASK, hdr) masks the port bits from 'hdr' before assignment to u8 'port'. The AR9331 switch has at most 6 ports, and the port number field in the header is only a few bits wide — well within u8 range after masking. dsa_conduit_find_user(ndev, 0, port) returns NULL for any port not registered as a DSA user, and the NULL check at line 82 causes safe packet drop. No counterexample possible: the mask prevents truncation loss, and the NULL guard prevents use of invalid port values. False positive.
ksz_xmit_timestamp() — net/dsa/tag_ksz.c FP confidence=high
The function reads a PTP correction field from a network packet, but the subsequent arithmetic is carefully bounded. The tv_sec field is masked to 2 bits, tv_nsec is guaranteed [0, 999999999] by ns_to_timespec64(), and the OR of these two values into tstamp_raw always fits within 32 bits with no overlap between the bit fields used.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | get_unaligned_be64() line 240 |
| Taint snippet | tstamp_raw = ((ts.tv_sec & 3) << 30) | ts.tv_nsec; |
| Tainted var | tstamp_raw |
| Truncation | line 240: 64 → 32-bit u32 |
| Sink snippet | tstamp_raw = ((ts.tv_sec & 3) << 30) | ts.tv_nsec; |
| Possibly guarded | no |
Dismissed: The taint originates from a network PTP packet, but the computation is bounded by design: (1) only the `correction < 0` branch executes; (2) ns_to_timespec64() guarantees tv_nsec in [0, 999999999] < 2^30; (3) tv_sec is masked with & 3 giving max 3; (4) (3 << 30) | 999999999 = 4221225471 which is well within u32 range (4294967295). The bit fields are non-overlapping (tv_sec uses bits 30-31, tv_nsec uses bits 0-29), so no counterexample exists that would overflow u32.
qca_tag_rcv() — net/dsa/tag_qca.c FP confidence=high
Both findings are false positives. FIELD_GET() is a kernel macro that extracts a bitfield using a mask and right-shift, so the result is always bounded by the bitfield width — not the full 16-bit source width. QCA_HDR_RECV_VERSION and QCA_HDR_RECV_TYPE are narrow bitfields within the 16-bit header; FIELD_GET() masks and shifts before assignment, meaning the resulting value in 'ver' and 'pk_type' is inherently limited to the number of bits defined by those field masks, regardless of the destination type being u8. The scanner's concern that a 16-bit value is silently truncated to 8-bit is not applicable because FIELD_GET() already performs the necessary masking. Furthermore, 'ver' is immediately checked against the expected constant QCA_HDR_VERSION, and 'pk_type' is compared against known enum values — any out-of-range value simply falls through to the 'port' extraction path, which is further validated by dsa_conduit_find_user() returning NULL on invalid ports.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohs() line 58 |
| Taint snippet | ver = FIELD_GET(QCA_HDR_RECV_VERSION, hdr); |
| Tainted var | ver |
| Truncation | line 58: 16 → 8-bit u8 |
| Sink snippet | ver = FIELD_GET(QCA_HDR_RECV_VERSION, hdr); |
| Possibly guarded | no |
Dismissed: FIELD_GET(QCA_HDR_RECV_VERSION, hdr) applies a compile-time mask and right-shift derived from the field definition, so the result is bounded to the bitfield width before being stored in 'ver'. The truncation from 16-bit to u8 is safe because FIELD_GET guarantees the value fits within the field width. Additionally, 'ver' is immediately compared to QCA_HDR_VERSION and the packet is dropped if they differ — no counterexample exists where an unexpected 'ver' value could cause harm beyond dropping the packet. False positive.
Finding #2 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohs() line 65 |
| Taint snippet | pk_type = FIELD_GET(QCA_HDR_RECV_TYPE, hdr); |
| Tainted var | pk_type |
| Truncation | line 65: 16 → 8-bit u8 |
| Sink snippet | pk_type = FIELD_GET(QCA_HDR_RECV_TYPE, hdr); |
| Possibly guarded | no |
Dismissed: FIELD_GET(QCA_HDR_RECV_TYPE, hdr) masks and shifts to extract only the type bitfield, so 'pk_type' is bounded by the field width before truncation to u8. The code checks 'pk_type' against QCA_HDR_RECV_TYPE_RW_REG_ACK and QCA_HDR_RECV_TYPE_MIB; any unexpected value falls through to port extraction, which is validated by dsa_conduit_find_user() returning NULL on an invalid port (causing the packet to be dropped). No counterexample can be constructed where pk_type causes an OOB or dangerous operation. False positive.
rtl4a_tag_rcv() — net/dsa/tag_rtl4_a.c FP confidence=high
Both findings are false positives. In finding #1, the RHS expression explicitly masks with & 0x0f before assignment to prot, limiting the value to 4 bits (0-15), which trivially fits in a u8. In finding #2, the RHS expression explicitly masks with & 0xff before assignment to port, which is exactly the range of a u8 (0-255). The masking in both cases is the canonical way to intentionally and safely narrow a wider value — the scanner's own note says to flag as false positive when masking limits the value to the destination width, which is exactly what happens here. No counterexample can be constructed for either case: for prot, any 16-bit input after the shift and & 0x0f yields 0-15, always safe in u8; for port, any 16-bit input after & 0xff yields 0-255, always safe in u8. The subsequent use of 'port' in dsa_conduit_find_user() is also safe because DSA port numbers are typically bounded by the switch's port count, and dsa_conduit_find_user() handles not-found ports by returning NULL (checked at line 103).
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohs() line 94 |
| Taint snippet | prot = (protport >> RTL4_A_PROTOCOL_SHIFT) & 0x0f; |
| Tainted var | prot |
| Truncation | line 94: 16 → 8-bit u8 |
| Sink snippet | prot = (protport >> RTL4_A_PROTOCOL_SHIFT) & 0x0f; |
| Possibly guarded | no |
Dismissed: The expression '(protport >> RTL4_A_PROTOCOL_SHIFT) & 0x0f' explicitly masks the shifted result to 4 bits before storing in prot (u8). The mask & 0x0f guarantees the value is in [0, 15], well within u8 range. No truncation hazard exists. Cannot construct a counterexample: for any 16-bit protport, after shifting and masking with 0x0f the result is always 0-15.
Finding #2 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohs() line 100 |
| Taint snippet | port = protport & 0xff; |
| Tainted var | port |
| Truncation | line 100: 16 → 8-bit u8 |
| Sink snippet | port = protport & 0xff; |
| Possibly guarded | no |
Dismissed: The expression 'protport & 0xff' explicitly masks to 8 bits before storing in port (u8). This is exactly the representable range of u8 (0-255), so no truncation or information loss occurs. The scanner's own false-positive note applies directly here: masking with & 0xFF limits the value to destination width. Cannot construct a counterexample: any 16-bit protport after & 0xff is 0-255, always representable in u8.
rtl8_4_read_tag() — net/dsa/tag_rtl8_4.c FP confidence=high
All three findings involve FIELD_GET() which masks the 16-bit ntohs() value to the specific bitfield defined by the mask before assignment to u8. The masking ensures no information is lost in truncation if the field is ≤8 bits wide. Furthermore: proto and reason are only used in equality comparisons (no memory ops), and port is used only as an argument to dsa_conduit_find_user() which returns NULL for invalid ports (handled with -ENOENT). No OOB access is possible.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohs() line 176 |
| Taint snippet | proto = FIELD_GET(RTL8_4_PROTOCOL, ntohs(tag16[1])); |
| Tainted var | proto |
| Truncation | line 176: 16 → 8-bit u8 |
| Sink snippet | proto = FIELD_GET(RTL8_4_PROTOCOL, ntohs(tag16[1])); |
| Possibly guarded | no |
Dismissed: FIELD_GET(RTL8_4_PROTOCOL, ntohs(tag16[1])) masks the value to the protocol bitfield before assignment. The result is only used in an equality comparison against RTL8_4_PROTOCOL_RTL8365MB with no memory operations. No counterexample can be constructed where a truncated value passes the check when the full value would not, because FIELD_GET's masking ensures only the relevant bits remain before any truncation occurs.
Finding #2 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohs() line 185 |
| Taint snippet | reason = FIELD_GET(RTL8_4_REASON, ntohs(tag16[1])); |
| Tainted var | reason |
| Truncation | line 185: 16 → 8-bit u8 |
| Sink snippet | reason = FIELD_GET(RTL8_4_REASON, ntohs(tag16[1])); |
| Possibly guarded | no |
Dismissed: FIELD_GET(RTL8_4_REASON, ntohs(tag16[1])) masks the value to the reason bitfield. The variable 'reason' is only compared against RTL8_4_REASON_TRAP with no array indexing or memory operations. Even if the value exceeds u8 range after masking (impossible if the field is ≤8 bits), the only consequence is a boolean branch decision — no memory safety issue exists.
Finding #3 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohs() line 188 |
| Taint snippet | port = FIELD_GET(RTL8_4_TX, ntohs(tag16[3])); |
| Tainted var | port |
| Truncation | line 188: 16 → 8-bit u8 |
| Sink snippet | port = FIELD_GET(RTL8_4_TX, ntohs(tag16[3])); |
| Possibly guarded | no |
Dismissed: FIELD_GET(RTL8_4_TX, ntohs(tag16[3])) masks the value to the TX port bitfield. The result is passed to dsa_conduit_find_user() which performs its own internal port validation and returns NULL for non-existent ports. The NULL return is explicitly checked (lines 190-195) with -ENOENT returned on failure. No counterexample exists: any port value that maps to no user device causes NULL return and early exit without any memory corruption.
sja1110_rcv_inband_control_extension() — net/dsa/tag_sja1105.c BUG confidence=medium
The function validates only the minimal SJA1110_HEADER_LEN bytes via pskb_may_pull before parsing rx_header. When the metadata path is taken (RX_HEADER_IS_METADATA), the n_ts field extracted from rx_header is used as an unbounded loop iteration count with no validation that (n_ts+1)*SJA1110_META_TSTAMP_SIZE bytes are actually available in the skb buffer.
Finding #1 — Category F — cross-function via sja1110_rcv_meta() — BUG oob_read
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 592 |
| Taint snippet | rx_header = ntohs(*(__be16 *)skb->data); |
| Tainted var | rx_header |
| Call site | line 598 — passes rx_header to sja1110_rcv_meta() |
| Call snippet | return sja1110_rcv_meta(skb, rx_header); |
| Loop | for_loop line 555 |
| Sink snippet | for (i = 0; i <= n_ts; i++) { |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in sja1110_rcv_meta() when a crafted packet has a large N_TS field causing the loop to read past the end of the skb data buffer
Fix: In sja1110_rcv_meta(), after extracting n_ts, validate that the skb contains at least SJA1110_HEADER_LEN + (n_ts + 1) * SJA1110_META_TSTAMP_SIZE bytes (using pskb_may_pull or by checking skb->len against the required length) before entering the loop.
CVE pattern: Network-supplied loop bound without buffer length validation — similar to various DSA/network tag parsing OOB read patterns
ethtool_cmis_cdb_execute_epl_cmd() — net/ethtool/cmis_cdb.c FP confidence=medium
The `args->req` struct is a CDB request being constructed by the kernel to send TO the optical module, not a response received FROM it. The `be16_to_cpu(args->req.epl_len)` converts a big-endian field that was set by the kernel's own firmware-update code (CMIS uses big-endian wire format even for locally-constructed requests). The loop bounds are controlled by kernel-supplied firmware image data, not by an external server. Additionally, the loops have structural termination conditions bounded by hardware page/offset constants (CMIS_CDB_EPL_PAGE_END, CMIS_CDB_EPL_FW_BLOCK_OFFSET_END) and a min() calculation that prevents oversized individual writes.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | be16_to_cpu() line 572 |
| Taint snippet | u16 epl_len = be16_to_cpu(args->req.epl_len); |
| Tainted var | epl_len |
| Loop | for_loop line 577 |
| Sink snippet | for (page = CMIS_CDB_EPL_PAGE_START; |
| Possibly guarded | no |
Dismissed: epl_len is stored in big-endian in the locally-constructed CDB request struct (CMIS protocol uses BE format). The kernel sets epl_len to the actual EPL data size before calling this function. The outer loop is also bounded by hardware constants (CMIS_CDB_EPL_PAGE_END). No counterexample possible given locally-controlled data. False positive per pattern #1.
Finding #2 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | be16_to_cpu() line 572 |
| Taint snippet | u16 epl_len = be16_to_cpu(args->req.epl_len); |
| Tainted var | epl_len |
| Loop | while_loop line 581 |
| Sink snippet | while (offset <= CMIS_CDB_EPL_FW_BLOCK_OFFSET_END && |
| Possibly guarded | no |
Dismissed: Same reasoning as finding #1. The inner while loop is additionally bounded by CMIS_CDB_EPL_FW_BLOCK_OFFSET_END (a hardware constant) and bytes_to_write is capped by min() of space_left and read_write_len_ext. The taint source is a locally-written big-endian field in a kernel-constructed request, not an externally-received value.
ethtool_copy_validate_indir() — net/ethtool/ioctl.c FP confidence=high
The 'size' parameter in ethtool_copy_validate_indir() originates from the kernel driver via ops->get_rxfh_indir_size(dev), not from user space. The destination buffer 'indir' is allocated with kcalloc() using that exact same kernel-controlled size. User-supplied sizes are validated against the kernel-known size before the call is made (line 1386 in ethtool_set_rxfh_indir). The copy_from_user uses the kernel-controlled size, which matches the allocation. No OOB is possible.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_from_user() line 1293 |
| Taint snippet | if (copy_from_user(indir, useraddr, array_size(size, sizeof(indir[0])))) |
| Tainted var | array_size(size, sizeof(indir[0])) |
| Unvalidated size | copy_from_user() arg 2 line 1293 — size array_size(size, sizeof(indir[0])) |
| Sink snippet | if (copy_from_user(indir, useraddr, array_size(size, sizeof(indir[0])))) |
| Possibly guarded | no |
Dismissed: The scanner confuses 'size' as user-supplied because it's a parameter, but at both call sites it is set to the kernel-driver-reported indir size (ops->get_rxfh_indir_size(dev)), and the destination buffer is kcalloc'd with that exact size. User-supplied sizes are gated by equality checks against the kernel size before reaching the call. No counterexample can be constructed — the copy size always matches the allocation size.
ethtool_get_dump_data() — net/ethtool/ioctl.c FP confidence=high
The value 'len' is computed as min(tmp.len, dump.len) where tmp.len is kernel/driver-supplied and dump.len is user-supplied. The min() operation ensures len never exceeds tmp.len, which is exactly the size of the vzalloc'd buffer. The copy_to_user copies at most tmp.len bytes from a tmp.len-sized buffer — no OOB is possible. The destination is userspace, so user-controlled destination addresses are the user's own concern.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 2835 |
| Taint snippet | if (copy_to_user(useraddr, data, len)) |
| Tainted var | len |
| Unvalidated size | copy_to_user() arg 2 line 2835 — size len |
| Sink snippet | if (copy_to_user(useraddr, data, len)) |
| Possibly guarded | no |
Dismissed: len = min(tmp.len, dump.len) where tmp.len is the driver-reported dump size used to size the vzalloc allocation. Since len ≤ tmp.len = size of data buffer, copy_to_user(useraddr, data, len) never reads beyond the allocated buffer. No counterexample exists: any user-supplied dump.len either gets capped by min() at tmp.len (safe) or is smaller than tmp.len (also safe). False positive from static analysis not modeling the min() constraint.
ethtool_get_features() — net/ethtool/ioctl.c FP confidence=high
The function reads copy_size from userspace but immediately clamps it at line 110-111 to at most ETHTOOL_DEV_FEATURE_WORDS before using it in the copy_to_user call. The source buffer 'features' has exactly ETHTOOL_DEV_FEATURE_WORDS elements, so copy_size * sizeof(*features) can never exceed the buffer size. The scanner failed to recognize this clamping as a sufficient bounds check.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 116 |
| Taint snippet | if (copy_to_user(useraddr, features, |
| Tainted var | array_size(copy_size, sizeof(*features)) |
| Unvalidated size | copy_to_user() arg 2 line 116 — size array_size(copy_size, sizeof(*features)) |
| Sink snippet | if (copy_to_user(useraddr, features, |
| Possibly guarded | no |
Dismissed: copy_size is read from userspace (user-controlled) but is clamped at lines 110-111: 'if (copy_size > ETHTOOL_DEV_FEATURE_WORDS) copy_size = ETHTOOL_DEV_FEATURE_WORDS'. Since copy_size is u32, it cannot be negative, and the upper bound is enforced before use. No counterexample can be constructed: any value > ETHTOOL_DEV_FEATURE_WORDS is reduced to ETHTOOL_DEV_FEATURE_WORDS, making array_size(copy_size, sizeof(*features)) always ≤ sizeof(features). The finding is a false positive.
ethtool_get_phy_stats() — net/ethtool/ioctl.c FP confidence=medium
stats.n_stats is read from userspace initially, but the helper functions ethtool_get_phy_stats_phydev() and ethtool_get_phy_stats_ethtool() overwrite stats.n_stats with the kernel-determined actual stat count and allocate data[] accordingly. The copy_to_user size thus reflects the kernel-controlled allocation size, not the raw user-supplied value. This is the standard ethtool pattern.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 2660 |
| Taint snippet | copy_to_user(useraddr, data, |
| Tainted var | array_size(stats.n_stats, sizeof(u64)) |
| Unvalidated size | copy_to_user() arg 2 line 2660 — size array_size(stats.n_stats, sizeof(u64)) |
| Sink snippet | copy_to_user(useraddr, data, |
| Possibly guarded | no |
Dismissed: stats.n_stats originates from copy_from_user, making the scanner flag it as tainted. However, the ethtool helper functions (ethtool_get_phy_stats_phydev/ethtool_get_phy_stats_ethtool) follow the standard ethtool pattern of overwriting stats.n_stats with the actual driver-reported count and allocating data[] with vzalloc(stats.n_stats * sizeof(u64)) based on that count. By the time copy_to_user is reached, stats.n_stats is kernel-controlled and matches the allocation. No counterexample exists where stats.n_stats could exceed the data[] buffer size at the copy_to_user call. The implicit n_stats > 0 guard at line 2659 also prevents zero-count copies.
ethtool_get_sset_info() — net/ethtool/ioctl.c FP confidence=high
The function is carefully designed: n_bits = hweight64(sset_mask) counts the set bits in the user-supplied mask (bounded 0-64). info_buf is allocated with exactly n_bits u32 slots. idx increments only when a bit from sset_mask is processed, so idx <= n_bits always. The copy_to_user at line 841 uses array_size(idx, sizeof(u32)) which is always <= n_bits*sizeof(u32) = the allocation size. No OOB is possible.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 841 |
| Taint snippet | if (copy_to_user(useraddr, info_buf, array_size(idx, sizeof(u32)))) |
| Tainted var | array_size(idx, sizeof(u32)) |
| Unvalidated size | copy_to_user() arg 2 line 841 — size array_size(idx, sizeof(u32)) |
| Sink snippet | if (copy_to_user(useraddr, info_buf, array_size(idx, sizeof(u32)))) |
| Possibly guarded | no |
Dismissed: idx is derived from user-supplied sset_mask but is structurally bounded: idx can increment at most once per set bit in sset_mask, and n_bits = hweight64(sset_mask) counts exactly those bits. info_buf is allocated with n_bits elements, so idx <= n_bits always holds. Attempted counterexample: no assignment of sset_mask can make idx exceed n_bits since the loop iterates only over bits already in sset_mask. The scanner correctly identifies the taint source but misses that idx is upper-bounded by n_bits (the allocation count). False positive.
ethtool_get_stats() — net/ethtool/ioctl.c FP confidence=high
The function reads stats.n_stats from userspace via copy_from_user, but immediately overwrites it at lines 2539-2542 with either 0 or the kernel-computed n_stats value (which itself is bounded by the S32_MAX/sizeof(u64) check at line 2533). The tainted user-supplied value is never actually used in any memory operation — only the sanitised kernel value is. The allocation size and copy size both use the same kernel-controlled stats.n_stats, so no overflow or mismatch is possible.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 2558 |
| Taint snippet | copy_to_user(useraddr, data, |
| Tainted var | array_size(stats.n_stats, sizeof(u64)) |
| Unvalidated size | copy_to_user() arg 2 line 2558 — size array_size(stats.n_stats, sizeof(u64)) |
| Sink snippet | copy_to_user(useraddr, data, |
| Possibly guarded | no |
Dismissed: stats.n_stats is unconditionally overwritten at lines 2539-2542 with either 0 or n_stats (the kernel-driver-returned value). The user-supplied stats.n_stats is used only as a hint to detect mismatch, never as a size operand. n_stats is bounded by the S32_MAX/sizeof(u64) check. The vzalloc and copy_to_user both use the same stats.n_stats value. No counterexample exists: any user-supplied stats.n_stats value results in stats.n_stats being set to 0 or n_stats, never an arbitrary large number. False positive.
ethtool_get_tunable() — net/ethtool/ioctl.c FP confidence=high
ethtool_tunable_valid() validates tuna.len against the expected size for the given tunable type and id before the allocation and copy. The kzalloc() allocates exactly tuna.len bytes, and copy_to_user() copies exactly tuna.len bytes from that allocation — no OOB is possible. The validator constrains tuna.len to small, known values matching specific tunable types.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 2984 |
| Taint snippet | if (copy_to_user(useraddr, data, tuna.len)) |
| Tainted var | tuna.len |
| Unvalidated size | copy_to_user() arg 2 line 2984 — size tuna.len |
| Sink snippet | if (copy_to_user(useraddr, data, tuna.len)) |
| Possibly guarded | no |
Dismissed: tuna.len is user-supplied, but ethtool_tunable_valid() checks that len matches the expected size for the given tunable id and type_id (e.g., sizeof(u32) for numeric tunables). After validation, kzalloc(tuna.len) allocates exactly tuna.len bytes, and copy_to_user uses the same tuna.len as the size — perfectly bounded to the allocation. No counterexample exists: any tuna.len passing the validator is a small, kernel-expected constant, and data is allocated to exactly that size.
ethtool_rxnfc_copy_from_compat() — net/ethtool/ioctl.c FP confidence=high
The function correctly caps the copy_from_user size with min(size, sizeof(crxnfc)), ensuring the copy never exceeds the stack-allocated destination buffer. The scanner flagged the user-controlled 'size' parameter, but the min() operation with sizeof(crxnfc) is precisely the correct and sufficient guard. No counterexample can be constructed where the copy exceeds the destination buffer.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_from_user() line 874 |
| Taint snippet | if (copy_from_user(&crxnfc, useraddr, min(size, sizeof(crxnfc)))) |
| Tainted var | min(size, sizeof(crxnfc)) |
| Unvalidated size | copy_from_user() arg 2 line 874 — size min(size, sizeof(crxnfc)) |
| Sink snippet | if (copy_from_user(&crxnfc, useraddr, min(size, sizeof(crxnfc)))) |
| Possibly guarded | no |
Dismissed: The 'size' argument is passed from kernel ioctl dispatch code (not directly read from user memory via get_user). Even treating it as user-controlled, min(size, sizeof(crxnfc)) clamps the copy to exactly the size of the stack-allocated destination buffer. Counterexample test: no value of size can cause OOB because min() guarantees the copy is at most sizeof(crxnfc) bytes. This is a textbook false positive — the scanner sees a variable size argument to copy_from_user but misses that min() with the buffer's own sizeof() is the correct mitigation.
ethtool_rxnfc_copy_from_user() — net/ethtool/ioctl.c FP confidence=high
The 'size' parameter in ethtool_rxnfc_copy_from_user() is not user-supplied — it is a kernel-internal value computed by ethtool_rxnfc_copy_struct(). The two values used are: (1) a compile-time offsetof+sizeof expression for ETHTOOL_GRXFH/SRXFH, and (2) sizeof(*info) for the full structure. Both are kernel-controlled constants derived from struct layout, not from any user-supplied data. The destination buffer 'rxnfc' (type struct ethtool_rxnfc *) is large enough to hold sizeof(*info) bytes by definition. There is no path where a user can influence the 'size' argument passed to copy_from_user().
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_from_user() line 903 |
| Taint snippet | if (copy_from_user(rxnfc, useraddr, size)) |
| Tainted var | size |
| Unvalidated size | copy_from_user() arg 2 line 903 — size size |
| Sink snippet | if (copy_from_user(rxnfc, useraddr, size)) |
| Possibly guarded | no |
Dismissed: The scanner flagged 'size' as tainted because copy_from_user() returns data from user space, but 'size' is not read from user space — it is the third argument (the length), which is determined entirely by kernel-internal computation in ethtool_rxnfc_copy_struct(). The two possible values are offsetof(struct ethtool_rxnfc, data)+sizeof(info->data) and sizeof(*info), both compile-time constants bounded by the allocated struct size. No counterexample exists because there is no user-controlled code path that can vary 'size'. This is a false positive from the taint tracker conflating the destination buffer (which receives user data) with the size argument.
ethtool_rxnfc_copy_to_compat() — net/ethtool/ioctl.c FP confidence=high
The `size` parameter passed to `copy_to_user()` is bounded by `min(size, sizeof(crxnfc))`, which ensures it never exceeds the size of the local stack buffer `crxnfc`. The `size` value comes from a kernel-internal path parameter, not directly from user-supplied data in an unchecked manner. The `min()` call is the safety guard: regardless of what `size` is, the actual bytes copied cannot exceed `sizeof(crxnfc)`. The source buffer is a local stack variable (`crxnfc`) fully initialized by the kernel. The scanner misidentifies `min(size, sizeof(crxnfc))` as unvalidated, but the `min()` itself IS the validation — it caps the size at the buffer's actual length. No counterexample can be constructed where `min(size, sizeof(crxnfc))` exceeds `sizeof(crxnfc)` — by definition of `min()`.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 932 |
| Taint snippet | if (copy_to_user(useraddr, &crxnfc, min(size, sizeof(crxnfc)))) |
| Tainted var | min(size, sizeof(crxnfc)) |
| Unvalidated size | copy_to_user() arg 2 line 932 — size min(size, sizeof(crxnfc)) |
| Sink snippet | if (copy_to_user(useraddr, &crxnfc, min(size, sizeof(crxnfc)))) |
| Possibly guarded | no |
Dismissed: The `min(size, sizeof(crxnfc))` expression is itself the bounds check. The size passed to `copy_to_user()` is capped at `sizeof(crxnfc)` (the size of the local stack buffer), so it cannot exceed the buffer. No counterexample exists: for any value of `size`, `min(size, sizeof(crxnfc)) <= sizeof(crxnfc)` always holds. The taint source is also misidentified — `size` is a kernel-internal parameter propagated from `ethtool_rxnfc_copy_to_user()`, not raw user input. This is a false positive from the static analyzer treating `min()` as insufficient when it is precisely sufficient.
ethtool_rxnfc_copy_to_user() — net/ethtool/ioctl.c FP confidence=high
Finding #1: 'size' is kernel-internal (sizeof of a kernel stack struct), not user-controlled. Finding #2: rule_cnt is user-supplied but validated before allocation (KMALLOC_MAX_SIZE / sizeof(u32) check); the copy_to_user uses the post-driver-call rule_cnt which could theoretically differ from pre-call count, but the driver is trusted kernel code. Both findings are effectively false positives from the external attack surface perspective.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 981 |
| Taint snippet | ret = copy_to_user(useraddr, rxnfc, size); |
| Tainted var | size |
| Unvalidated size | copy_to_user() arg 2 line 981 — size size |
| Sink snippet | ret = copy_to_user(useraddr, rxnfc, size); |
| Possibly guarded | no |
Dismissed: The 'size' parameter originates from 'info_size = sizeof(info)' in all callers — a compile-time constant bounded by the stack-allocated struct ethtool_rxnfc. Not user-controlled. False positive.
Finding #2 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 989 |
| Taint snippet | if (copy_to_user(useraddr, rule_buf, |
| Tainted var | rxnfc->rule_cnt * sizeof(u32) |
| Unvalidated size | copy_to_user() arg 2 line 989 — size rxnfc->rule_cnt * sizeof(u32) |
| Sink snippet | if (copy_to_user(useraddr, rule_buf, |
| Possibly guarded | no |
Dismissed: rule_cnt is user-supplied but validated at allocation time: only allocated if rule_cnt <= KMALLOC_MAX_SIZE/sizeof(u32), and rule_buf is NULL if 0 or exceeds limit. The copy_to_user uses post-driver rule_cnt which could change, but drivers are trusted. No counterexample exists from untrusted userspace: any user-supplied rule_cnt passes through the allocation guard, ensuring the buffer is large enough for the original count. A driver incorrectly increasing rule_cnt would be a driver bug in trusted code.
ethtool_self_test() — net/ethtool/ioctl.c FP confidence=high
The function correctly overwrites the user-supplied test.len at line 2368 with the kernel-derived test_len from get_sset_count(). The allocation at line 2369 and the copy_to_user at line 2381 both use this kernel-controlled value. The static analyzer incorrectly tracks test.len as user-controlled because the struct was initially populated via copy_from_user, missing the unconditional overwrite at line 2368.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 2381 |
| Taint snippet | if (copy_to_user(useraddr, data, array_size(test.len, sizeof(u64)))) |
| Tainted var | array_size(test.len, sizeof(u64)) |
| Unvalidated size | copy_to_user() arg 2 line 2381 — size array_size(test.len, sizeof(u64)) |
| Sink snippet | if (copy_to_user(useraddr, data, array_size(test.len, sizeof(u64)))) |
| Possibly guarded | no |
Dismissed: test.len is set to test_len (a kernel-internal value from get_sset_count()) at line 2368, unconditionally overwriting the user-supplied value. The copy_to_user at line 2381 therefore uses a kernel-controlled size that exactly matches the kcalloc'd buffer size. No counterexample can be constructed because user input cannot influence test.len at the point of the flagged copy_to_user call.
get_phy_tunable() — net/ethtool/ioctl.c FP confidence=high
tuna.len is user-supplied but validated by ethtool_phy_tunable_valid() before use. The allocation kzalloc(tuna.len) and subsequent copy_to_user(..., tuna.len) are symmetric — no kernel OOB is possible since the buffer is exactly tuna.len bytes. The validator ensures tuna.len matches the expected size for the specific tunable ID, bounding it to small known values.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 3189 |
| Taint snippet | if (copy_to_user(useraddr, data, tuna.len)) |
| Tainted var | tuna.len |
| Unvalidated size | copy_to_user() arg 2 line 3189 — size tuna.len |
| Sink snippet | if (copy_to_user(useraddr, data, tuna.len)) |
| Possibly guarded | no |
Dismissed: ethtool_phy_tunable_valid() validates tuna.len against the expected size for the tunable type before the allocation and copy. The kernel buffer 'data' is allocated to exactly tuna.len bytes, so copy_to_user(useraddr, data, tuna.len) cannot read beyond the allocated buffer. No counterexample exists: any tuna.len that passes validation is a small fixed constant, and data is allocated to that same size. False positive.
dgram_getsockopt() — net/ieee802154/socket.c FP confidence=high
The 'len' variable is user-supplied (via get_user) but is immediately clamped by min_t(unsigned int, len, sizeof(int)) on line 847. sizeof(int) is 4. So len is guaranteed to be in [0, 4] before it reaches copy_to_user. The destination buffer &val is an int on the stack (4 bytes), so copying at most 4 bytes into it is safe. The scanner missed the min_t clamp as a sufficient guard.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 876 |
| Taint snippet | if (copy_to_user(optval, &val, len)) |
| Tainted var | len |
| Unvalidated size | copy_to_user() arg 2 line 876 — size len |
| Sink snippet | if (copy_to_user(optval, &val, len)) |
| Possibly guarded | no |
Dismissed: len is read from userspace via get_user, making it user-controlled. However, line 847 clamps it: len = min_t(unsigned int, len, sizeof(int)), so len is in [0, 4]. The copy_to_user call copies at most 4 bytes from &val (a local int, 4 bytes). No counterexample exists: any user-supplied len value, after the clamp, cannot exceed sizeof(int)=4, which is exactly the size of val. The finding is a false positive.
cipso_v4_map_cat_enum_ntoh() — net/ipv4/cipso_ipv4.c FP confidence=high
cipso_v4_map_cat_enum_ntoh() reads category bits from a network packet and passes them to netlbl_catmap_setbit(). While the value is genuinely network-supplied, netlbl_catmap_setbit() is specifically designed to accept any u32 bit position: _netlbl_catmap_getnode() with _CM_F_ALLOC allocates catmap nodes as needed, ensuring the returned node's startbit aligns such that bit-startbit is always within NETLBL_CATMAP_SIZE bounds. No counterexample can be constructed — the catmap API safely handles the full u16 range (0-65535).
Finding #1 — Category C — cross-function via netlbl_catmap_setbit() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | get_unaligned_be16() line 982 |
| Taint snippet | ret_val = netlbl_catmap_setbit(&secattr->attr.mls.cat, |
| Tainted var | get_unaligned_be16(&net_cat[iter]) |
| Call site | line 982 — passes get_unaligned_be16(&net_cat[iter]) to netlbl_catmap_setbit() |
| Call snippet | ret_val = netlbl_catmap_setbit(&secattr->attr.mls.cat, |
| Subscript (in callee) | [] line 788 |
| Sink snippet | iter->bitmap[idx] |= NETLBL_CATMAP_BIT << (bit % NETLBL_CATMAP_MAPSIZE); |
| Possibly guarded | no |
Dismissed: netlbl_catmap_setbit() is a purpose-built API for setting arbitrary bit positions in a sparse bitmap structure. It internally calls _netlbl_catmap_getnode(_CM_F_ALLOC) which allocates and links catmap nodes as needed, setting startbit appropriately so that bit-startbit always falls within the node's bitmap[] array bounds. No OOB is possible regardless of the input u16 category value. The scanner flagged the bitmap[] subscript as a sink, but that subscript is bounded by the catmap node allocation logic. This is a false positive.
cipso_v4_map_cat_rng_ntoh() — net/ipv4/cipso_ipv4.c FP confidence=high
The scanner flagged cat_low and cat_high (network-supplied u16 values) as controlling a 'dangerous loop bound' in netlbl_catmap_setrng(). However, that loop does not index a fixed-size buffer — it iterates through a logical category range and dynamically allocates catmap bitmap nodes via netlbl_catmap_setbit()/netlbl_catmap_setlong(). The u16 values are inherently bounded to [0, 65535], preventing integer overflow. If cat_low > cat_high (start > end), the while loop condition 'spot <= end' is immediately false, so no iterations occur. The catmap structure grows dynamically as needed, making this safe for any u16 range. At most this represents a theoretical DoS (large range → many small allocations), not an OOB memory vulnerability.
Finding #1 — Category F — cross-function via netlbl_catmap_setrng() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | get_unaligned_be16() line 1118 |
| Taint snippet | cat_low = get_unaligned_be16(&net_cat[net_iter + 2]); |
| Tainted var | cat_low |
| Call site | line 1122 — passes cat_low to netlbl_catmap_setrng() |
| Call snippet | ret_val = netlbl_catmap_setrng(&secattr->attr.mls.cat, |
| Loop | while_loop line 814 |
| Sink snippet | while (rc == 0 && spot <= end) { |
| Possibly guarded | no |
Dismissed: cat_low is a network-supplied u16 value passed as 'start' to netlbl_catmap_setrng(). The function iterates from start to end setting bits in a dynamically allocated catmap structure — no fixed-size buffer is indexed. No counterexample exists that causes OOB access: any u16 value (0-65535) is safe because the catmap allocates nodes dynamically. False positive.
Finding #2 — Category F — cross-function via netlbl_catmap_setrng() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | get_unaligned_be16() line 1116 |
| Taint snippet | cat_high = get_unaligned_be16(&net_cat[net_iter]); |
| Tainted var | cat_high |
| Call site | line 1122 — passes cat_high to netlbl_catmap_setrng() |
| Call snippet | ret_val = netlbl_catmap_setrng(&secattr->attr.mls.cat, |
| Loop | while_loop line 814 |
| Sink snippet | while (rc == 0 && spot <= end) { |
| Possibly guarded | no |
Dismissed: cat_high is a network-supplied u16 value passed as 'end' to netlbl_catmap_setrng(). Same analysis as finding #1: the loop in netlbl_catmap_setrng() dynamically allocates catmap nodes for any range, so no OOB access is possible. If cat_high < cat_low (end < start), the loop exits immediately. If cat_high = 65535 (maximum u16), the loop may iterate many times but only performs safe dynamic allocations. False positive.
rtentry_to_fib_config() — net/ipv4/fib_frontend.c FP confidence=high
The scanner misidentified the size argument IFNAMSIZ-1 as user-controlled/tainted. IFNAMSIZ is a compile-time kernel constant (16), so IFNAMSIZ-1 is a fixed value 15. The copy_from_user() call copies at most 15 bytes into a devname[IFNAMSIZ] (16-byte) buffer, which is perfectly safe. The source pointer rt->rt_dev is user-supplied, but the size is entirely kernel-controlled. This is a textbook false positive from the taint analysis conflating the source pointer taint with the size argument.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_from_user() line 542 |
| Taint snippet | if (copy_from_user(devname, rt->rt_dev, IFNAMSIZ-1)) |
| Tainted var | IFNAMSIZ-1 |
| Unvalidated size | copy_from_user() arg 2 line 542 — size IFNAMSIZ-1 |
| Sink snippet | if (copy_from_user(devname, rt->rt_dev, IFNAMSIZ-1)) |
| Possibly guarded | no |
Dismissed: IFNAMSIZ is a kernel compile-time constant (#define IFNAMSIZ 16), so IFNAMSIZ-1 equals 15 — a fixed, kernel-internal value that is not user-controlled in any way. The destination buffer devname[IFNAMSIZ] has 16 bytes of capacity, so copying IFNAMSIZ-1=15 bytes is always safe and cannot overflow. The scanner appears to have propagated taint from rt->rt_dev (the user-supplied source pointer) onto the size argument, which is incorrect — the size is not derived from any user-supplied value. No counterexample exists where this causes OOB access, confirming this is a false positive.
fib_table_insert() — net/ipv4/fib_trie.c FP confidence=high
The function handles routing table insertion with kernel-internal data structures. The fib_alias fields are maintained by the kernel, not populated from network packets. The scanner incorrectly propagated ntohl() taint through unrelated internal struct fields.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohl() line 1283 |
| Taint snippet | state = READ_ONCE(fa->fa_state); |
| Tainted var | state |
| Truncation | line 1283: 32 → 8-bit u8 |
| Sink snippet | state = READ_ONCE(fa->fa_state); |
| Possibly guarded | yes (heuristic) |
Dismissed: fa->fa_state is a u8 field in struct fib_alias, which is a kernel-internal routing table data structure — not a network-received buffer. The READ_ONCE() reads a u8 into a u8 local variable (state is declared u8 on line 1265), so there is no truncation at all. The scanner incorrectly propagated ntohl() taint (from network address processing) through internal struct fields via alias lookup. No counterexample can be constructed because the source and destination types are identical (u8), making truncation impossible.
fib_table_lookup() — net/ipv4/fib_trie.c FP confidence=high
The trie lookup function uses the network-supplied destination address (key) to index into trie nodes. While the key is technically tainted, the trie data structure maintains the invariant that each node's child array has exactly 1<<bits entries and that get_index(pkey, pn) returns a value in [0, 1<<pn->bits) due to how parent-child relationships are established during trie construction. The cindex &= cindex-1 operation at line 1537 only decreases the value. The bounds check at line 1464 ensures index < (1<<n->bits) during forward traversal, establishing trie structural invariants. The use at line 1540 is protected by these invariants — no concrete counterexample exists where cindex could exceed pn->tnode array bounds without violating trie structural invariants established at node insertion time.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohl() line 1427 |
| Taint snippet | const t_key key = ntohl(flp->daddr); |
| Tainted var | cindex |
| Subscript | [] line 1540 |
| Sink snippet | cptr = &pn->tnode[cindex]; |
| Possibly guarded | no |
Dismissed: The key (from ntohl(flp->daddr)) flows into cindex through get_index() which computes (key ^ pn->key) >> pn->pos. The trie structural invariant guarantees this result fits within pn->bits bits (i.e., is < 1<<pn->bits), because parent-child relationships in the trie are established such that the bits at positions [pos, pos+bits) uniquely identify the child. The operation cindex &= cindex-1 at line 1537 clears the LSB, making the value strictly smaller — so if cindex was in-bounds before, it remains in-bounds. No counterexample can be constructed: any cindex value ≥ 1<<pn->bits would require a corrupted trie node, not merely a crafted key. This is a false positive from the static analyzer not understanding trie structural invariants.
icmp_build_probe() — net/ipv4/icmp.c FP confidence=high
The function has solid validation discipline. ident_len is derived from network data but is bounded by the check at lines 1324-1326 (must be > 0 and <= sizeof(_iio) - sizeof(iio->extobj_hdr)). The second skb_header_pointer call at line 1328 copies exactly sizeof(iio->extobj_hdr)+ident_len bytes into the stack buffer _iio, so all subsequent iio->* accesses are within a locally-held stack buffer. Per-case ident_len checks (IFNAMSIZ, sizeof(ifindex), sizeof(ctype3_hdr)+addrlen) provide additional validation. dev/in_dev/in6_dev are kernel-internal struct pointers from device lookups, not derived from packet offsets.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 1327 |
| Taint snippet | ident_len = ntohs(iio->extobj_hdr.length) - sizeof(iio->extobj_hdr); |
| Tainted var | iio |
| Pointer deref | iio->extobj_hdr line 1329 |
| Sink snippet | sizeof(iio->extobj_hdr) + ident_len, &_iio); |
| Possibly guarded | no |
Dismissed: iio points to stack buffer _iio after skb_header_pointer at line 1328. ident_len is bounded to [1, sizeof(_iio)-sizeof(extobj_hdr)] by the check at lines 1324-1326. No counterexample possible: ident_len cannot exceed sizeof(_iio)-sizeof(extobj_hdr), so the copy into _iio is always within bounds.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 1327 |
| Taint snippet | ident_len = ntohs(iio->extobj_hdr.length) - sizeof(iio->extobj_hdr); |
| Tainted var | iio |
| Pointer deref | iio->extobj_hdr line 1335 |
| Sink snippet | switch (iio->extobj_hdr.class_type) { |
| Possibly guarded | no |
Dismissed: iio->extobj_hdr.class_type at line 1335: iio points to _iio stack buffer. The buffer was filled by skb_header_pointer with exactly sizeof(extobj_hdr)+ident_len bytes, all within sizeof(_iio). Access is safe.
Finding #3 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohs() line 1327 |
| Taint snippet | ident_len = ntohs(iio->extobj_hdr.length) - sizeof(iio->extobj_hdr); |
| Tainted var | ident_len |
| Sink | memcpy() line 1340 (arg 2, role=size) |
| Sink snippet | memcpy(buff, &iio->ident.name, ident_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: ident_len is checked < IFNAMSIZ at line 1337 (destination bound) and is bounded by sizeof(_iio)-sizeof(extobj_hdr) (source bound via skb_header_pointer). No counterexample: ident_len must be < IFNAMSIZ to reach memcpy, and buff is IFNAMSIZ bytes. Both OOB read and OOB write are prevented.
Finding #4 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | ntohs() line 1327 |
| Taint snippet | ident_len = ntohs(iio->extobj_hdr.length) - sizeof(iio->extobj_hdr); |
| Tainted var | iio |
| Sink | memcpy() line 1340 (arg 1, role=pointer) |
| Sink snippet | memcpy(buff, &iio->ident.name, ident_len); |
| Possibly guarded | no |
Dismissed: &iio->ident.name is within the _iio stack buffer. skb_header_pointer validated that the packet contained enough data and copied it into _iio. ident_len < IFNAMSIZ ensures no OOB read of _iio.
Finding #5 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 1327 |
| Taint snippet | ident_len = ntohs(iio->extobj_hdr.length) - sizeof(iio->extobj_hdr); |
| Tainted var | iio |
| Pointer deref | iio->ident line 1340 |
| Sink snippet | memcpy(buff, &iio->ident.name, ident_len); |
| Possibly guarded | no |
Dismissed: Same as #4. iio->ident is within _iio stack buffer. ident_len < IFNAMSIZ guard at line 1337 ensures the name field is within bounds.
Finding #6 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 1327 |
| Taint snippet | ident_len = ntohs(iio->extobj_hdr.length) - sizeof(iio->extobj_hdr); |
| Tainted var | iio |
| Pointer deref | iio->ident line 1344 |
| Sink snippet | if (ident_len != sizeof(iio->ident.ifindex)) |
| Possibly guarded | no |
Dismissed: sizeof(iio->ident.ifindex) is a compile-time constant used for comparison only. iio points to _iio stack buffer. Access is safe.
Finding #7 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 1327 |
| Taint snippet | ident_len = ntohs(iio->extobj_hdr.length) - sizeof(iio->extobj_hdr); |
| Tainted var | iio |
| Pointer deref | iio->ident line 1346 |
| Sink snippet | dev = dev_get_by_index(net, ntohl(iio->ident.ifindex)); |
| Possibly guarded | yes (heuristic) |
Dismissed: Line 1344 checks ident_len == sizeof(iio->ident.ifindex), so ifindex is within the validated _iio buffer. dev_get_by_index receives an integer value, not a raw packet pointer. False positive.
Finding #8 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 1327 |
| Taint snippet | ident_len = ntohs(iio->extobj_hdr.length) - sizeof(iio->extobj_hdr); |
| Tainted var | iio |
| Pointer deref | iio->ident line 1349 |
| Sink snippet | if (ident_len < sizeof(iio->ident.addr.ctype3_hdr) || |
| Possibly guarded | yes (heuristic) |
Dismissed: Lines 1349-1352 validate ident_len >= sizeof(ctype3_hdr) and exactly equals sizeof(ctype3_hdr)+addrlen. iio is in _iio stack buffer. Access is safe.
Finding #9 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 1327 |
| Taint snippet | ident_len = ntohs(iio->extobj_hdr.length) - sizeof(iio->extobj_hdr); |
| Tainted var | iio |
| Pointer deref | iio->ident line 1350 |
| Sink snippet | ident_len != sizeof(iio->ident.addr.ctype3_hdr) + |
| Possibly guarded | yes (heuristic) |
Dismissed: Same guard as #8. False positive.
Finding #10 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 1327 |
| Taint snippet | ident_len = ntohs(iio->extobj_hdr.length) - sizeof(iio->extobj_hdr); |
| Tainted var | iio |
| Pointer deref | iio->ident line 1351 |
| Sink snippet | iio->ident.addr.ctype3_hdr.addrlen) |
| Possibly guarded | yes (heuristic) |
Dismissed: addrlen is read from _iio stack buffer at line 1351 for comparison only (not used as a size for allocation or copy). The ctype3_hdr is within the validated buffer. False positive.
Finding #11 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 1327 |
| Taint snippet | ident_len = ntohs(iio->extobj_hdr.length) - sizeof(iio->extobj_hdr); |
| Tainted var | iio |
| Pointer deref | iio->ident line 1353 |
| Sink snippet | switch (ntohs(iio->ident.addr.ctype3_hdr.afi)) { |
| Possibly guarded | yes (heuristic) |
Dismissed: ntohs(iio->ident.addr.ctype3_hdr.afi) is read from _iio stack buffer after validation. Used only as a switch discriminant. False positive.
Finding #12 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 1327 |
| Taint snippet | ident_len = ntohs(iio->extobj_hdr.length) - sizeof(iio->extobj_hdr); |
| Tainted var | iio |
| Pointer deref | iio->ident line 1355 |
| Sink snippet | if (iio->ident.addr.ctype3_hdr.addrlen != sizeof(struct in_addr)) |
| Possibly guarded | yes (heuristic) |
Dismissed: addrlen compared against sizeof(struct in_addr) — a compile-time constant. No memory operation sized by this. False positive.
Finding #13 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 1327 |
| Taint snippet | ident_len = ntohs(iio->extobj_hdr.length) - sizeof(iio->extobj_hdr); |
| Tainted var | iio |
| Pointer deref | iio->ident line 1357 |
| Sink snippet | dev = ip_dev_find(net, iio->ident.addr.ip_addr.ipv4_addr); |
| Possibly guarded | yes (heuristic) |
Dismissed: ip_dev_find receives a 4-byte IPv4 address from _iio stack buffer. addrlen was validated == sizeof(struct in_addr) at line 1355. ip_addr.ipv4_addr is within the validated buffer. False positive.
Finding #14 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 1327 |
| Taint snippet | ident_len = ntohs(iio->extobj_hdr.length) - sizeof(iio->extobj_hdr); |
| Tainted var | iio |
| Pointer deref | iio->ident line 1361 |
| Sink snippet | if (iio->ident.addr.ctype3_hdr.addrlen != sizeof(struct in6_addr)) |
| Possibly guarded | yes (heuristic) |
Dismissed: addrlen compared against sizeof(struct in6_addr). Access to _iio stack buffer within bounds. False positive.
Finding #15 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 1327 |
| Taint snippet | ident_len = ntohs(iio->extobj_hdr.length) - sizeof(iio->extobj_hdr); |
| Tainted var | iio |
| Pointer deref | iio->ident line 1363 |
| Sink snippet | dev = ipv6_dev_find(net, &iio->ident.addr.ip_addr.ipv6_addr, dev); |
| Possibly guarded | yes (heuristic) |
Dismissed: ipv6_dev_find receives pointer to in6_addr within _iio stack buffer. addrlen validated == sizeof(struct in6_addr) at line 1361. False positive.
Finding #16 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 1327 |
| Taint snippet | ident_len = ntohs(iio->extobj_hdr.length) - sizeof(iio->extobj_hdr); |
| Tainted var | dev |
| Pointer deref | dev->flags line 1379 |
| Sink snippet | if (dev->flags & IFF_UP) |
| Possibly guarded | no |
Dismissed: dev is a kernel-internal net_device pointer returned by dev_get_by_name/dev_get_by_index/ip_dev_find/ipv6_dev_find. It is not derived from packet offset arithmetic. The taint propagation to dev->flags is a false positive from over-approximation in the static analyzer.
Finding #17 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 1327 |
| Taint snippet | ident_len = ntohs(iio->extobj_hdr.length) - sizeof(iio->extobj_hdr); |
| Tainted var | in_dev |
| Pointer deref | in_dev->ifa_list line 1383 |
| Sink snippet | if (in_dev && rcu_access_pointer(in_dev->ifa_list)) |
| Possibly guarded | no |
Dismissed: in_dev is obtained via __in_dev_get_rcu(dev) where dev is a kernel net_device pointer. Not derived from packet data. False positive from taint over-approximation.
Finding #18 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 1327 |
| Taint snippet | ident_len = ntohs(iio->extobj_hdr.length) - sizeof(iio->extobj_hdr); |
| Tainted var | in6_dev |
| Pointer deref | in6_dev->addr_list line 1387 |
| Sink snippet | if (in6_dev && !list_empty(&in6_dev->addr_list)) |
| Possibly guarded | no |
Dismissed: in6_dev is obtained via __in6_dev_get(dev) where dev is a kernel net_device pointer. Not derived from packet data. False positive from taint over-approximation.
igmp_heard_query() — net/ipv4/igmp.c FP confidence=high
igmp_heard_query() has proper v3 path validation: pskb_may_pull() is called with sizeof(igmpv3_query) + ntohs(ih3->nsrcs)*sizeof(__be32) before ih3 is re-derived and nsrcs is used. This guarantees the srcs array in the packet has sufficient contiguous bytes for the loop in igmp_marksources(). The static analysis flagged a network-supplied loop bound, but the bounds check is present and sufficient.
Finding #1 — Category F — cross-function via igmp_marksources() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 1093 |
| Taint snippet | igmp_marksources(im, ntohs(ih3->nsrcs), ih3->srcs); |
| Tainted var | ntohs(ih3->nsrcs) |
| Call site | line 1093 — passes ntohs(ih3->nsrcs) to igmp_marksources() |
| Call snippet | igmp_marksources(im, ntohs(ih3->nsrcs), ih3->srcs); |
| Loop | for_loop line 933 |
| Sink snippet | for (i = 0; i < nsrcs; i++) |
| Possibly guarded | yes (heuristic) |
Dismissed: The pskb_may_pull() at lines 1030-1032 validates that the skb contains sizeof(igmpv3_query) + ntohs(ih3->nsrcs)*sizeof(__be32) contiguous bytes. After this check passes and ih3 is re-derived at line 1033, accessing srcs[i] for i in [0, nsrcs) is safe because the buffer is guaranteed to hold all nsrcs source addresses. No counterexample can be constructed: any nsrcs value that passes pskb_may_pull() has sufficient backing data for the loop. False positive.
tcp_v4_early_demux() — net/ipv4/ip_input.c FP confidence=high
tcp_v4_early_demux() properly validates the TCP header presence and minimum doff before use. The network-supplied port value flows into __inet_lookup_established(), but the array subscript is computed as 'hash & hashinfo->ehash_mask', which is a power-of-2 mask bounding the slot to the valid array range regardless of any input value. This is the standard Linux hash table lookup pattern and is not exploitable.
Finding #1 — Category C — cross-function via __inet_lookup_established() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 342 |
| Taint snippet | sk = __inet_lookup_established(net, iph->saddr, th->source, |
| Tainted var | ntohs(th->dest) |
| Call site | line 342 — passes ntohs(th->dest) to __inet_lookup_established() |
| Call snippet | sk = __inet_lookup_established(net, iph->saddr, th->source, |
| Subscript (in callee) | [] line 546 |
| Sink snippet | head = &hashinfo->ehash[slot]; |
| Possibly guarded | no |
Dismissed: The array subscript 'slot = hash & hashinfo->ehash_mask' applies a bitmask that unconditionally bounds the index to [0, ehash_mask], which equals [0, ehash_size-1]. No counterexample exists: any value of ntohs(th->dest) fed through inet_ehashfn() and then ANDed with ehash_mask produces a valid array index. The scanner incorrectly flagged this standard masked-hash-table-lookup pattern as a potential OOB access.
ic_bootp_recv() — net/ipv4/ipconfig.c FP confidence=medium
The function has a solid validation chain: skb->len >= ntohs(h->tot_len) at line 1019, pskb_may_pull linearizing the full skb at line 1040, and ext_len >= 0 check. The 'end' pointer is therefore bounded to within the actual packet buffer. Both loops use 'ext < end' as their condition, preventing reads past end. The per-option length field (*ext) controls advancement within the loop, but both loops check 'if (ext >= end) break' (finding #1) or 'if (ext < end)' (finding #2) after advancement, so no dereference occurs past end. The real concern would be opt[1]/opt[2] accesses near the boundary, but these are bounded by the same end pointer checks.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 1071 |
| Taint snippet | u8 *end = (u8 *) b + ntohs(b->iph.tot_len); |
| Tainted var | end |
| Loop | while_loop line 1080 |
| Sink snippet | while (ext < end && *ext != 0xff) { |
| Possibly guarded | no |
Dismissed: The 'end' pointer is derived from ntohs(b->iph.tot_len), which is validated at line 1019 to be <= skb->len, and pskb_may_pull at line 1040 ensures the full skb is linearized. So 'end' does not exceed the actual buffer. The loop at line 1080 checks 'ext < end' before dereferencing *ext, and checks 'if (ext >= end) break' after advancing. Could not construct a counterexample where a valid packet causes OOB: any tot_len large enough to move end past the buffer is caught by line 1019.
Finding #2 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 1071 |
| Taint snippet | u8 *end = (u8 *) b + ntohs(b->iph.tot_len); |
| Tainted var | end |
| Loop | while_loop line 1143 |
| Sink snippet | while (ext < end && *ext != 0xff) { |
| Possibly guarded | yes (heuristic) |
Dismissed: Same analysis as finding #1. The loop at line 1143 checks 'ext < end' before dereferencing, and 'if (ext < end)' guards the ic_do_bootp_ext call. The 'end' boundary is itself validated against the actual buffer via line 1019 + pskb_may_pull. No counterexample could be constructed.
__do_replace() — net/ipv4/netfilter/arp_tables.c FP confidence=high
The `num_counters` value originates from user space (via `tmp.num_counters` in the calling functions), but both call sites validate it before passing it to `__do_replace()`. Specifically, both `do_replace()` and `compat_do_replace()` check: (1) `tmp.num_counters >= INT_MAX / sizeof(struct xt_counters)` → return -ENOMEM (overflow guard), and (2) `tmp.num_counters == 0` → return -EINVAL. The `counters` buffer is allocated via `xt_counters_alloc(num_counters)` inside `__do_replace()`, which allocates exactly `num_counters * sizeof(struct xt_counters)` bytes. The subsequent `copy_to_user()` call copies exactly `sizeof(struct xt_counters) * num_counters` bytes from that same buffer. Since the allocation size matches the copy size and the overflow check is done before the call, the copy cannot exceed the allocated buffer. No counterexample exists: any value that passes the overflow guard and non-zero check will result in a properly sized allocation that matches the copy size.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 944 |
| Taint snippet | if (copy_to_user(counters_ptr, counters, |
| Tainted var | sizeof(struct xt_counters) * num_counters |
| Unvalidated size | copy_to_user() arg 2 line 944 — size sizeof(struct xt_counters) * num_counters |
| Sink snippet | if (copy_to_user(counters_ptr, counters, |
| Possibly guarded | no |
Dismissed: Both call sites perform: (1) overflow check `num_counters >= INT_MAX / sizeof(struct xt_counters)` preventing multiplication overflow, and (2) zero check `num_counters == 0`. Inside `__do_replace()`, `xt_counters_alloc(num_counters)` allocates exactly `num_counters * sizeof(struct xt_counters)` bytes. The `copy_to_user()` at line 944 copies the same quantity. Since allocation size equals copy size and the overflow is precluded, no OOB write is possible. Counterexample attempt: to pass the guards, `num_counters` must be in range [1, INT_MAX/sizeof(xt_counters)-1]. For any such value, allocation matches copy size. No counterexample found.
__do_replace() — net/ipv4/netfilter/ip_tables.c FP confidence=high
The num_counters value is user-supplied (from tmp.num_counters read via copy_from_sockptr), but both call sites validate it before calling __do_replace(). Specifically: (1) tmp.num_counters == 0 is rejected, (2) tmp.num_counters >= INT_MAX / sizeof(struct xt_counters) is rejected (overflow check), and (3) xt_counters_alloc(num_counters) at line 1044 allocates exactly num_counters * sizeof(struct xt_counters) bytes. The copy_to_user at line 1083 copies sizeof(struct xt_counters) * num_counters bytes from the counters buffer, which was allocated with exactly that size. No counterexample can be constructed: any value that passes the overflow check and non-zero check results in a correctly-sized allocation, and the copy_to_user uses the same count. The finding is a false positive.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 1083 |
| Taint snippet | if (copy_to_user(counters_ptr, counters, |
| Tainted var | sizeof(struct xt_counters) * num_counters |
| Unvalidated size | copy_to_user() arg 2 line 1083 — size sizeof(struct xt_counters) * num_counters |
| Sink snippet | if (copy_to_user(counters_ptr, counters, |
| Possibly guarded | no |
Dismissed: num_counters is user-supplied but validated at both call sites: zero is rejected, and overflow (num_counters >= INT_MAX / sizeof(struct xt_counters)) is rejected. xt_counters_alloc() allocates exactly num_counters * sizeof(struct xt_counters) bytes. The copy_to_user uses the identical expression as the allocation, so the copy never exceeds the allocated buffer. No counterexample exists where a value passes all guards yet causes OOB — the allocation and copy use the same count, and the count is bounded by INT_MAX / sizeof(struct xt_counters).
pptp_inbound_pkt() — net/ipv4/netfilter/nf_nat_pptp.c FP confidence=high
The pptp_msg_name() function explicitly validates its argument before using it as an array subscript. The check 'if (msg > PPTP_MSG_MAX) return pptp_msg_name_array[0]' ensures that only values <= PPTP_MSG_MAX are used as subscripts. Any out-of-range value returns the safe index 0. This is a classic validation-function pattern — the internal array access IS the validated path, not a vulnerable sink.
Finding #1 — Category C — cross-function via pptp_msg_name() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 256 |
| Taint snippet | switch (msg = ntohs(ctlh->messageType)) { |
| Tainted var | msg |
| Call site | line 276 — passes msg to pptp_msg_name() |
| Call snippet | pr_debug("unknown inbound packet %s\n", pptp_msg_name(msg)); |
| Subscript (in callee) | [] line 77 |
| Sink snippet | return pptp_msg_name_array[msg]; |
| Possibly guarded | yes (heuristic) |
Dismissed: pptp_msg_name() checks 'if (msg > PPTP_MSG_MAX) return pptp_msg_name_array[0]' before using msg as a subscript. No counterexample exists: any msg value either returns index 0 (safe) or returns pptp_msg_name_array[msg] where msg <= PPTP_MSG_MAX (also safe). The callee itself is the validation + safe access function. False positive.
pptp_outbound_pkt() — net/ipv4/netfilter/nf_nat_pptp.c FP confidence=high
The flagged flow involves msg (from ntohs(ctlh->messageType)) being passed to pptp_msg_name(). The callee pptp_msg_name() explicitly checks 'if (msg > PPTP_MSG_MAX) return pptp_msg_name_array[0];' before indexing the array. This is a complete and sufficient bounds check internal to the callee. No counterexample can be constructed: any msg > PPTP_MSG_MAX returns the safe [0] element, and any msg <= PPTP_MSG_MAX is a valid index. The function is a validation/name-lookup helper that guards its own parameter.
Finding #1 — Category C — cross-function via pptp_msg_name() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 147 |
| Taint snippet | switch (msg = ntohs(ctlh->messageType)) { |
| Tainted var | msg |
| Call site | line 173 — passes msg to pptp_msg_name() |
| Call snippet | pptp_msg_name(msg)); |
| Subscript (in callee) | [] line 77 |
| Sink snippet | return pptp_msg_name_array[msg]; |
| Possibly guarded | yes (heuristic) |
Dismissed: pptp_msg_name() validates its argument with 'if (msg > PPTP_MSG_MAX) return pptp_msg_name_array[0];' before the array subscript at line 77. No value of msg (u_int16_t, 0..65535) can cause OOB access: values above PPTP_MSG_MAX return the safe [0] element, values at or below PPTP_MSG_MAX are valid indices. Cannot construct a counterexample that passes the guard yet causes OOB. This is a false positive — the callee performs its own internal bounds check.
__cookie_v4_check() — net/ipv4/syncookies.c FP confidence=high
The scanner flagged mssind as tainted and used as an array subscript without a bounds check, but missed the explicit bounds check on line 193. The ternary expression `mssind < ARRAY_SIZE(msstab) ? msstab[mssind] : 0` is a textbook bounds-checked array access. Furthermore, check_tcp_syn_cookie() returns ((__u32)-1) = 0xFFFFFFFF if the cookie fails age validation, which will fail the `< ARRAY_SIZE(msstab)` test and return 0 safely. The COOKIEMASK further limits the return value to low bits. No counterexample can be constructed that passes the guard yet causes OOB access.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohl() line 186 |
| Taint snippet | __u32 cookie = ntohl(th->ack_seq) - 1; |
| Tainted var | mssind |
| Subscript | [] line 193 |
| Sink snippet | return mssind < ARRAY_SIZE(msstab) ? msstab[mssind] : 0; |
| Possibly guarded | no |
Dismissed: The ternary on line 193 is `mssind < ARRAY_SIZE(msstab) ? msstab[mssind] : 0` — this IS a proper bounds check. The scanner apparently marked it as 'possibly guarded: no', which is incorrect; the guard is directly in the same expression as the sink. check_tcp_syn_cookie() returns 0xFFFFFFFF on failure (which fails the bounds check) or a value masked by COOKIEMASK (which limits it to a small number of bits). No counterexample exists: any value >= ARRAY_SIZE(msstab) returns 0, any value < ARRAY_SIZE(msstab) is safe to use as a subscript. False positive.
tcp_v4_err() — net/ipv4/tcp_ipv4.c FP confidence=high
The flagged flow traces ntohs(th->source) through __inet_lookup_established() to a hash table array subscript. The key insight is that the 'slot' used as the subscript is computed as 'hash & hashinfo->ehash_mask', which is a bitmask operation that inherently bounds the result to valid indices of the ehash array (ehash_mask is set to ehash_size-1 at allocation time). The ntohs() value is merely one input to inet_ehashfn() which produces a hash, and the subsequent AND with ehash_mask makes OOB access impossible regardless of the input value. This is standard hash table indexing in the Linux kernel and is not a vulnerability.
Finding #1 — Category C — cross-function via __inet_lookup_established() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 504 |
| Taint snippet | sk = __inet_lookup_established(net, iph->daddr, th->dest, iph->saddr, |
| Tainted var | ntohs(th->source) |
| Call site | line 504 — passes ntohs(th->source) to __inet_lookup_established() |
| Call snippet | sk = __inet_lookup_established(net, iph->daddr, th->dest, iph->saddr, |
| Subscript (in callee) | [] line 546 |
| Sink snippet | head = &hashinfo->ehash[slot]; |
| Possibly guarded | no |
Dismissed: The tainted value ntohs(th->source) is genuinely network-supplied (from the TCP header of an ICMP error payload). However, inside __inet_lookup_established(), the value is passed to inet_ehashfn() which produces a cryptographic/hash output, and then 'slot = hash & hashinfo->ehash_mask' bounds the result to [0, ehash_mask]. Since ehash_mask == ehash_size - 1 (a power-of-two minus one), the AND operation guarantees slot is always a valid index into hashinfo->ehash[]. No counterexample exists where a network-supplied port value could cause slot >= ehash_size, because the bitmask is an absolute bound. This is the standard Linux kernel hash table indexing pattern and is safe by design.
tcp4_check_fraglist_gro() — net/ipv4/tcp_offload.c FP confidence=high
The tainted network-supplied port value (ntohs(th->dest)) is used as input to inet_ehashfn(), a hash function. The result is then masked with ehash_mask before use as an array subscript ('slot = hash & hashinfo->ehash_mask'), which unconditionally bounds the index to valid array positions. This is the standard safe hash table lookup pattern in the Linux kernel. The scanner cannot see through the hash + mask indirection and incorrectly flags the array access as potentially OOB.
Finding #1 — Category C — cross-function via __inet_lookup_established() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 410 |
| Taint snippet | sk = __inet_lookup_established(net, iph->saddr, th->source, |
| Tainted var | ntohs(th->dest) |
| Call site | line 410 — passes ntohs(th->dest) to __inet_lookup_established() |
| Call snippet | sk = __inet_lookup_established(net, iph->saddr, th->source, |
| Subscript (in callee) | [] line 546 |
| Sink snippet | head = &hashinfo->ehash[slot]; |
| Possibly guarded | no |
Dismissed: ntohs(th->dest) is network-supplied (u16, range 0–65535), but it is never used directly as an array index. It flows into inet_ehashfn() as one of several inputs to produce a hash value, and the result is then bounded by 'slot = hash & hashinfo->ehash_mask'. The mask operation guarantees slot < ehash array size regardless of any input value. No counterexample is possible: any value of th->dest produces a hash that, after masking, is a valid index. False positive due to taint tracking not modeling the hash+mask bounding pattern.
__udp4_lib_lookup() — net/ipv4/udp.c FP confidence=high
Finding #1 is a false positive because the provided udp4_lib_lookup4() is a stub returning NULL with no array access. Finding #2 is a false positive because udp_hashfn() masks hnum with udptable->mask (hnum & mask), guaranteeing the slot index is always within the allocated hash[] array bounds regardless of the input value.
Finding #1 — Category C — cross-function via udp4_lib_lookup4() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 672 |
| Taint snippet | unsigned short hnum = ntohs(dport); |
| Tainted var | hnum |
| Call site | line 681 — passes hnum to udp4_lib_lookup4() |
| Call snippet | result = udp4_lib_lookup4(net, saddr, sport, daddr, hnum, |
| Subscript (in callee) | [] line 549 |
| Sink snippet | hslot4 = &udptable->hash4[slot]; |
| Possibly guarded | no |
Dismissed: The provided callee source for udp4_lib_lookup4() is a stub that returns NULL unconditionally (line 522). There is no array subscript operation in the callee as provided. The scanner's reference to line 549 does not match the included source. This is a false positive — the scanner may have analyzed a different version or a different configuration of this function.
Finding #2 — Category C — cross-function via udp4_lib_lookup1() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 672 |
| Taint snippet | unsigned short hnum = ntohs(dport); |
| Tainted var | hnum |
| Call site | line 726 — passes hnum to udp4_lib_lookup1() |
| Call snippet | result = udp4_lib_lookup1(net, saddr, sport, daddr, hnum, dif, sdif, |
| Subscript (in callee) | [] line 441 |
| Sink snippet | struct udp_hslot *hslot = &udptable->hash[slot]; |
| Possibly guarded | no |
Dismissed: Inside udp4_lib_lookup1(), hnum is passed to udp_hashfn(net, hnum, udptable->mask) which computes hnum & udptable->mask. The resulting slot is always in the range [0, mask], and the hash[] array has mask+1 entries. No counterexample exists: for any 16-bit hnum value, slot = hnum & mask <= mask, which is within bounds. The masking operation is a complete and sufficient bounds constraint.
__udp4_lib_mcast_deliver() — net/ipv4/udp.c FP confidence=high
The function correctly bounds hash2 via '& udptable->mask' before using it as an array index. The mask is set to (hash2_table_size - 1), so the bitwise AND guarantees hash2 < hash2_table_size, making the array access safe. The static analyzer tracked taint from ntohs() through ipv4_portaddr_hash() but failed to recognize the masking operation as a sufficient bounds check.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 2470 |
| Taint snippet | unsigned short hnum = ntohs(uh->dest); |
| Tainted var | hash2 |
| Subscript | [] line 2490 |
| Sink snippet | hslot = &udptable->hash2[hash2].hslot; |
| Possibly guarded | no |
Dismissed: hash2 is computed as 'ipv4_portaddr_hash(net, daddr, hnum) & udptable->mask'. The mask is (hash2_table_size - 1) for a power-of-two sized table, so hash2 is always in [0, mask], which is exactly the valid index range for udptable->hash2[]. No counterexample is constructible: any value of hnum/daddr after masking will produce an in-bounds index. This is the standard Linux hashtable lookup pattern and the finding is a false positive from the static analyzer not modeling bitwise-AND as a bounds constraint.
__udp4_lib_mcast_demux_lookup() — net/ipv4/udp.c FP confidence=high
The slot value is computed by udp_hashfn() which applies '& mask' (where mask = hash_size - 1), guaranteeing the result is always a valid index into udptable->hash[]. This is a standard power-of-two hash table indexing pattern that provides inherent bounds safety regardless of the input port value.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 2705 |
| Taint snippet | unsigned short hnum = ntohs(loc_port); |
| Tainted var | slot |
| Subscript | [] line 2711 |
| Sink snippet | hslot = &udptable->hash[slot]; |
| Possibly guarded | no |
Dismissed: Although loc_port is network-supplied, the array index is computed as udp_hashfn(net, hnum, udptable->mask) which internally performs '(hnum + net_hash_mix(net)) & mask'. The bitwise AND with mask (= hash_size - 1) clamps the result to [0, hash_size-1], exactly the valid range of udptable->hash[]. No counterexample exists: any value of hnum, after being ANDed with mask, must be <= mask < hash_size. The static scanner missed this implicit bounds guarantee embedded in the hash function.
esp6_find_tcp_sk() — net/ipv6/esp6.c FP confidence=high
The taint source (encap->encap_sport) is from an XFRM state configured via local netlink, not a server-supplied network packet. More importantly, the flagged sink uses slot = hash & hashinfo->ehash_mask, which is a standard hashtable masking operation that unconditionally constrains the array index to valid bounds regardless of the input port value. No counterexample exists that would cause OOB access.
Finding #1 — Category C — cross-function via __inet6_lookup_established() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 155 |
| Taint snippet | sk = __inet6_lookup_established(net, &x->id.daddr.in6, dport, |
| Tainted var | ntohs(sport) |
| Call site | line 155 — passes ntohs(sport) to __inet6_lookup_established() |
| Call snippet | sk = __inet6_lookup_established(net, &x->id.daddr.in6, dport, |
| Subscript (in callee) | [] line 101 |
| Sink snippet | head = &hashinfo->ehash[slot]; |
| Possibly guarded | no |
Dismissed: sport is from x->encap (kernel-internal XFRM state, configured via privileged netlink), not a server network response. The array subscript 'slot = hash & hashinfo->ehash_mask' masks with ehash_mask which equals ehash_size-1 (power-of-2 bound), making it impossible for slot to exceed array bounds regardless of port value. No counterexample can be constructed. This is a standard safe hashtable lookup pattern; the scanner is flagging a false positive on the hash&mask idiom.
tcp_v6_early_demux() — net/ipv6/ip6_input.c FP confidence=high
tcp_v6_early_demux() performs appropriate header validation (pskb_may_pull, doff check) before extracting TCP header fields. The flagged cross-function 'array subscript' sink in __inet6_lookup_established() is fully protected by the hash masking operation: slot = hash & hashinfo->ehash_mask always produces a valid index within the ehash array, regardless of the network-supplied port value. This is a standard, correct hash-table lookup pattern throughout the Linux kernel.
Finding #1 — Category C — cross-function via __inet6_lookup_established() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 70 |
| Taint snippet | sk = __inet6_lookup_established(net, &hdr->saddr, th->source, |
| Tainted var | ntohs(th->dest) |
| Call site | line 70 — passes ntohs(th->dest) to __inet6_lookup_established() |
| Call snippet | sk = __inet6_lookup_established(net, &hdr->saddr, th->source, |
| Subscript (in callee) | [] line 101 |
| Sink snippet | head = &hashinfo->ehash[slot]; |
| Possibly guarded | no |
Dismissed: The tainted value ntohs(th->dest) is a network-supplied port number (genuinely external). However, inside __inet6_lookup_established(), it is passed through inet6_ehashfn() to produce a hash, then masked with 'hash & hashinfo->ehash_mask' before use as an array index. Since ehash is allocated with ehash_mask+1 entries, the mask operation unconditionally constrains slot to [0, ehash_mask]. No counterexample exists: any u16 port value, after hashing and masking, yields a valid in-bounds slot. The scanner flagged a taint flow to an array subscript without recognizing that the masking operation is the bounds check.
__do_replace() — net/ipv6/netfilter/ip6_tables.c FP confidence=high
num_counters is user-supplied but is validated before __do_replace() is called. Both call sites check: (1) num_counters >= INT_MAX / sizeof(struct xt_counters) — overflow guard, (2) num_counters == 0 — zero guard. The allocation at line 1061 uses xt_counters_alloc(num_counters) which allocates exactly num_counters * sizeof(struct xt_counters) bytes. The copy_to_user at line 1100 copies exactly sizeof(struct xt_counters) * num_counters bytes from that same allocation — so the size matches the allocation size by construction. No counterexample can be constructed: any value that passes the overflow check and non-zero check will produce an allocation at least as large as the copy size.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 1100 |
| Taint snippet | if (copy_to_user(counters_ptr, counters, |
| Tainted var | sizeof(struct xt_counters) * num_counters |
| Unvalidated size | copy_to_user() arg 2 line 1100 — size sizeof(struct xt_counters) * num_counters |
| Sink snippet | if (copy_to_user(counters_ptr, counters, |
| Possibly guarded | no |
Dismissed: num_counters comes from user-supplied struct ip6t_replace/compat_ip6t_replace. Both callers validate: (a) num_counters >= INT_MAX/sizeof(struct xt_counters) → -ENOMEM, and (b) num_counters == 0 → -EINVAL. The counters buffer is allocated via xt_counters_alloc(num_counters) which allocates num_counters * sizeof(struct xt_counters) bytes. The copy_to_user uses the identical expression sizeof(struct xt_counters) * num_counters. Since the allocation and the copy use the same validated num_counters, the copy never exceeds the buffer. No counterexample exists: to overflow the multiplication one would need num_counters >= INT_MAX/sizeof(xt_counters), which is blocked. This is a false positive.
nf_tproxy_get_sock_v6() — net/ipv6/netfilter/nf_tproxy_ipv6.c FP confidence=high
Both findings are false positives. The taint analysis has misidentified the nature of the data and the semantics of the operations involved. Finding #1 misunderstands that 'sk' is a pointer to a kernel socket struct returned by a kernel lookup function — not a pointer derived from a network packet offset. Finding #2 misunderstands that ntohs(dport) is used as a hash input, not a direct array subscript, and that 'slot' is masked by ehash_mask before use.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 96 |
| Taint snippet | sk = inet6_lookup_listener(net, skb, |
| Tainted var | sk |
| Pointer deref | sk->sk_refcnt line 102 |
| Sink snippet | if (sk && !refcount_inc_not_zero(&sk->sk_refcnt)) |
| Possibly guarded | no |
Dismissed: The taint source is ntohs() applied to 'dport', which is a __be16 function parameter — but 'sk' is NOT derived from 'dport' via offset arithmetic into a packet buffer. 'sk' is the return value of inet6_lookup_listener(), a kernel internal socket lookup function that returns a pointer to a kernel-managed 'struct sock' object. The access sk->sk_refcnt at line 102 is guarded by 'if (sk && ...)' which ensures sk is non-NULL before dereferencing. This is standard reference-count safe-increment pattern in the kernel. The scanner incorrectly traced 'ntohs()' taint through the lookup call to the returned sk pointer, which is a spurious taint propagation.
Finding #2 — Category C — cross-function via __inet6_lookup_established() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 111 |
| Taint snippet | sk = __inet6_lookup_established(net, saddr, sport, daddr, |
| Tainted var | ntohs(dport) |
| Call site | line 111 — passes ntohs(dport) to __inet6_lookup_established() |
| Call snippet | sk = __inet6_lookup_established(net, saddr, sport, daddr, |
| Subscript (in callee) | [] line 101 |
| Sink snippet | head = &hashinfo->ehash[slot]; |
| Possibly guarded | no |
Dismissed: ntohs(dport) is a port number (0-65535) passed to __inet6_lookup_established(). Inside that function, it is used as input to inet6_ehashfn() to compute a hash, and the resulting hash is masked by 'hashinfo->ehash_mask' (line 100: slot = hash & hashinfo->ehash_mask) before being used as an array index. The masking operation fully constrains 'slot' to valid indices within the ehash table regardless of the port value. No counterexample exists where a port value in [0, 65535] could cause an out-of-bounds access, because the AND with ehash_mask guarantees slot < table size. This is a standard safe hash-table lookup pattern; the finding is a false positive.
do_rawv6_getsockopt() — net/ipv6/raw.c FP confidence=high
The tainted value 'len' is read from userspace via get_user() at line 1060, making it user-supplied. However, line 1083 clamps it with min_t(unsigned int, sizeof(int), len), which caps it at sizeof(int) == 4 bytes. The destination buffer is &val (a local int on the stack), which is exactly 4 bytes. After the clamp, len cannot exceed 4, so copy_to_user() cannot read beyond &val. No counterexample exists that passes the min_t guard and still causes OOB — the check is both present and sufficient.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 1087 |
| Taint snippet | if (copy_to_user(optval, &val, len)) |
| Tainted var | len |
| Unvalidated size | copy_to_user() arg 2 line 1087 — size len |
| Sink snippet | if (copy_to_user(optval, &val, len)) |
| Possibly guarded | no |
Dismissed: len is clamped by min_t(unsigned int, sizeof(int), len) at line 1083 before being used in copy_to_user(). Since sizeof(int)==4 and &val is a local int (4 bytes), any user-supplied len is safely bounded to [0,4]. No counterexample can be constructed: even INT_MAX or a negative value (unsigned comparison forces it >= sizeof(int), so min picks sizeof(int)) results in len==4, which exactly matches the source buffer size. The scanner missed the clamping guard, making this a false positive.
ipip6_tunnel_get_prl() — net/ipv6/sit.c FP confidence=high
The flagged `len` variable in copy_to_user() is not directly user-controlled; it is computed as `sizeof(*kp) * c` where `c` is the actual count of entries written into the kernel-allocated `kp` buffer during the loop. The loop is bounded by `cmax` (derived from user-supplied datalen/sizeof) and by the actual PRL list length. The fallback allocation uses `ca = min(t->prl_count, cmax)`, and since the loop traverses at most `t->prl_count` entries and breaks at `c >= cmax`, `c <= ca` always holds in that path. Thus `len` never exceeds the allocated `kp` buffer size in either allocation path. The copy_to_user destination is in user space (the user's responsibility to provide sufficient space), and no kernel OOB write occurs.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 363 |
| Taint snippet | if ((len && copy_to_user(a + 1, kp, len)) || put_user(len, &a->datalen)) |
| Tainted var | len |
| Unvalidated size | copy_to_user() arg 2 line 363 — size len |
| Sink snippet | if ((len && copy_to_user(a + 1, kp, len)) || put_user(len, &a->datalen)) |
| Possibly guarded | no |
Dismissed: len = sizeof(*kp) * c, where c is bounded by min(t->prl_count, cmax) due to the loop guard (c >= cmax causes break) and the finite PRL list length. The kp buffer is allocated for max(cmax, ca) entries in the first path and ca=min(t->prl_count,cmax) entries in the fallback path — in both cases len <= allocation size. No counterexample can be constructed where len exceeds the kp buffer. The scanner incorrectly traces datalen taint through the division and min() without recognizing the effective bounding. False positive.
__cookie_v6_check() — net/ipv6/syncookies.c FP confidence=high
The scanner flagged mssind as tainted from ntohl() and used as an array subscript without bounds check. However, mssind is NOT directly derived from the network-supplied cookie; it is the RETURN VALUE of check_tcp_syn_cookie(), which performs cryptographic verification and returns either a bounded COOKIEMASK-masked value or (__u32)-1 on failure. The line 127 guard 'mssind < ARRAY_SIZE(msstab)' is a proper bounds check: if mssind >= ARRAY_SIZE(msstab) (including the (__u32)-1 failure sentinel), the function returns 0 without indexing the array. No counterexample can be constructed where mssind passes the guard and is out of bounds.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohl() line 120 |
| Taint snippet | __u32 cookie = ntohl(th->ack_seq) - 1; |
| Tainted var | mssind |
| Subscript | [] line 127 |
| Sink snippet | return mssind < ARRAY_SIZE(msstab) ? msstab[mssind] : 0; |
| Possibly guarded | no |
Dismissed: mssind receives its value from check_tcp_syn_cookie(), which either returns (__u32)-1 (0xFFFFFFFF) on age/validity failure or a value masked by COOKIEMASK (low bits of the verified cookie). The ternary guard 'mssind < ARRAY_SIZE(msstab)' at line 127 is a sufficient bounds check: any value >= ARRAY_SIZE(msstab) — including the 0xFFFFFFFF sentinel — causes the expression to evaluate to 0 rather than indexing the array. Counterexample attempt: to pass the guard, mssind must be strictly less than ARRAY_SIZE(msstab), which by definition makes the array access in-bounds. No counterexample exists. False positive.
tcp_v6_err() — net/ipv6/tcp_ipv6.c FP confidence=high
The flagged flow is a standard hash-table lookup pattern in the kernel's TCP socket infrastructure. The value ntohs(th->source) is indeed derived from a network packet, but its use as an array subscript is protected by a hash computation and masking operation. The 'slot' value used as the subscript is computed as 'hash & hashinfo->ehash_mask', where ehash_mask is set to (ehash_size - 1), guaranteeing the slot always falls within bounds of the ehash array. The raw port number is never directly used as an array index — it is fed into inet6_ehashfn() which produces a hash, and that hash is then masked. This is a textbook false positive where the scanner tracks taint through a hash function without recognizing that the mask operation bounds the result.
Finding #1 — Category C — cross-function via __inet6_lookup_established() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 417 |
| Taint snippet | sk = __inet6_lookup_established(net, &hdr->daddr, th->dest, |
| Tainted var | ntohs(th->source) |
| Call site | line 417 — passes ntohs(th->source) to __inet6_lookup_established() |
| Call snippet | sk = __inet6_lookup_established(net, &hdr->daddr, th->dest, |
| Subscript (in callee) | [] line 101 |
| Sink snippet | head = &hashinfo->ehash[slot]; |
| Possibly guarded | no |
Dismissed: The tainted value ntohs(th->source) is a 16-bit port number read from the incoming ICMP-encapsulated TCP header. However, it is never used directly as an array subscript. Inside __inet6_lookup_established(), the port is passed to inet6_ehashfn() which computes a hash, and then 'slot = hash & hashinfo->ehash_mask' masks the hash to the valid index range. Since ehash_mask = ehash_size - 1 (a bitmask), slot is always in [0, ehash_size-1], making 'hashinfo->ehash[slot]' safe. No counterexample exists: no value of th->source can produce an out-of-bounds slot because the bitwise AND with ehash_mask is a hard upper bound. This is a canonical false positive from taint tracking through a hash+mask pattern.
tcp6_check_fraglist_gro() — net/ipv6/tcpv6_offload.c FP confidence=high
The tainted value ntohs(th->dest) is a network port number extracted from a received TCP header. Inside __inet6_lookup_established(), it is used as the 'hnum' parameter in inet6_ehashfn() to compute a hash, and then 'slot = hash & hashinfo->ehash_mask' masks the hash result before indexing into ehash[slot]. The '& ehash_mask' operation is a classic power-of-two bitmask that constrains the slot to [0, ehash_mask], which is always within the allocated ehash array bounds. This is standard kernel hash-table lookup discipline — the raw port value is never used directly as an array index; it goes through a hash function and then a bitmask. There is no OOB access possible regardless of what value ntohs(th->dest) holds.
Finding #1 — Category C — cross-function via __inet6_lookup_established() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 36 |
| Taint snippet | sk = __inet6_lookup_established(net, &hdr->saddr, th->source, |
| Tainted var | ntohs(th->dest) |
| Call site | line 36 — passes ntohs(th->dest) to __inet6_lookup_established() |
| Call snippet | sk = __inet6_lookup_established(net, &hdr->saddr, th->source, |
| Subscript (in callee) | [] line 101 |
| Sink snippet | head = &hashinfo->ehash[slot]; |
| Possibly guarded | no |
Dismissed: The tainted port value ntohs(th->dest) is passed as 'hnum' to __inet6_lookup_established(), where it is fed into inet6_ehashfn() to produce a 32-bit hash. The slot is then computed as 'hash & hashinfo->ehash_mask', which constrains the index to the valid range of the ehash array regardless of the input value. No counterexample exists: any value of ntohs(th->dest) (0–65535) yields a hash that, after masking with ehash_mask, stays within the allocated array. The static scanner flagged the array subscript use but failed to see the intermediate masking step that makes the access safe.
__udp6_lib_lookup() — net/ipv6/udp.c FP confidence=high
Both findings are false positives. Finding #1: the provided source for udp6_lib_lookup4() shows it unconditionally returns NULL with no array access. The scanner's reference to a sink at line 305 does not match the provided source. Finding #2: udp_hashfn() applies a bitmask (hnum & udptable->mask) to constrain the slot index to the valid range of udptable->hash[], making OOB impossible regardless of the input value of hnum. This is standard kernel hash table practice.
Finding #1 — Category C — cross-function via udp6_lib_lookup4() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 351 |
| Taint snippet | unsigned short hnum = ntohs(dport); |
| Tainted var | hnum |
| Call site | line 360 — passes hnum to udp6_lib_lookup4() |
| Call snippet | result = udp6_lib_lookup4(net, saddr, sport, daddr, hnum, |
| Subscript (in callee) | [] line 305 |
| Sink snippet | hslot4 = &udptable->hash4[slot]; |
| Possibly guarded | no |
Dismissed: The provided source of udp6_lib_lookup4() shows it returns NULL unconditionally. The scanner references a sink at line 305 that does not exist in the provided callee body. False positive due to stale or incorrect line reference — the function has no array access.
Finding #2 — Category C — cross-function via udp6_lib_lookup1() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 351 |
| Taint snippet | unsigned short hnum = ntohs(dport); |
| Tainted var | hnum |
| Call site | line 399 — passes hnum to udp6_lib_lookup1() |
| Call snippet | result = udp6_lib_lookup1(net, saddr, sport, daddr, hnum, dif, sdif, |
| Subscript (in callee) | [] line 203 |
| Sink snippet | struct udp_hslot *hslot = &udptable->hash[slot]; |
| Possibly guarded | no |
Dismissed: udp_hashfn(net, hnum, udptable->mask) computes 'hnum & mask' (standard kernel implementation), which constrains slot to [0, mask] — exactly the valid index range of udptable->hash[]. No counterexample exists: any 16-bit hnum value, after masking with udptable->mask, yields a valid index. False positive.
__udp6_lib_mcast_deliver() — net/ipv6/udp.c FP confidence=high
The hash2 array subscript is bounded by '& udptable->mask' before use. Since udptable->hash2 has exactly (mask+1) entries, the ANDed value is always a valid index. This is the standard power-of-two hash table indexing idiom used throughout Linux networking. No counterexample can be constructed that would pass the mask operation yet produce an out-of-bounds index.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 944 |
| Taint snippet | unsigned short hnum = ntohs(uh->dest); |
| Tainted var | hash2 |
| Subscript | [] line 964 |
| Sink snippet | hslot = &udptable->hash2[hash2].hslot; |
| Possibly guarded | no |
Dismissed: hash2 = ipv6_portaddr_hash(net, daddr, hnum) & udptable->mask (line 962) constrains hash2 to [0, mask]. The hash2 array has mask+1 entries (power-of-two allocation), so hash2 is always a valid subscript. No counterexample exists: any input value of hnum, after passing through ipv6_portaddr_hash and being ANDed with mask, yields a value strictly less than the array size. This is a false positive from the static analyzer not modeling the mask-as-modulo pattern.
l2tp_udp_encap_recv() — net/l2tp/l2tp_core.c FP confidence=high
The scanner transitively taints 'session' through the server-supplied session_id used as a lookup key in l2tp_v3_session_get(). However, the returned session struct is a kernel-internal object whose fields (peer_cookie_len, peer_cookie) are set during local session configuration, not populated from the incoming network packet. Additionally, l2tp_v3_ensure_opt_in_linear() validates that the skb contains at least peer_cookie_len bytes before l2tp_recv_common() is called, so the memcmp is safe.
Finding #1 — Category B — cross-function via l2tp_recv_common() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohl() line 1073 |
| Taint snippet | session_id = ntohl(*(__be32 *)ptr); |
| Tainted var | session |
| Call site | line 1101 — passes session to l2tp_recv_common() |
| Call snippet | l2tp_recv_common(session, skb, ptr, optr, hdrflags, length); |
| Sink (in callee) | memcmp() line 874 (arg 2, role=size) |
| Sink snippet | if (memcmp(ptr, &session->peer_cookie[0], session->peer_cookie_len)) { |
| Possibly guarded | yes (heuristic) |
Dismissed: The taint propagation is a false positive. 'session_id' is server-supplied, but 'session' is a kernel-internal struct looked up by that ID from a kernel IDR. session->peer_cookie_len is a locally-configured field, not a network-supplied value. Furthermore, l2tp_v3_ensure_opt_in_linear() (called at line 1096, before l2tp_recv_common() at line 1101) calls pskb_may_pull() with off+opt_len where opt_len includes peer_cookie_len, ensuring the packet buffer covers the bytes that memcmp will read. No counterexample exists: an attacker cannot craft peer_cookie_len to an arbitrary value via the incoming packet.
l2tp_ip_recv() — net/l2tp/l2tp_ip.c FP confidence=high
The function correctly validates the skb buffer via pskb_may_pull (line 139) and l2tp_v3_ensure_opt_in_linear() before calling l2tp_recv_common(). The taint chain session_id → session → session->peer_cookie_len is a false positive: session_id is only used as a lookup key into a kernel IDR table; the returned session struct was created during L2TP session establishment (control plane), and its peer_cookie_len field is not directly populated from this received data packet. The scanner incorrectly propagates taint through the IDR lookup to all fields of the fetched kernel object.
Finding #1 — Category B — cross-function via l2tp_recv_common() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohl() line 145 |
| Taint snippet | session_id = ntohl(*((__be32 *)ptr)); |
| Tainted var | session |
| Call site | line 169 — passes session to l2tp_recv_common() |
| Call snippet | l2tp_recv_common(session, skb, ptr, optr, 0, skb->len); |
| Sink (in callee) | memcmp() line 874 (arg 2, role=size) |
| Sink snippet | if (memcmp(ptr, &session->peer_cookie[0], session->peer_cookie_len)) { |
| Possibly guarded | yes (heuristic) |
Dismissed: session_id from the packet is used only as an IDR lookup key. The returned session object is a kernel-internal struct whose peer_cookie_len was set during session creation via the L2TP control plane, not read from this data packet. l2tp_v3_ensure_opt_in_linear() validates that pskb_may_pull(skb, off + peer_cookie_len + l2specific_len) succeeds before l2tp_recv_common() is called, ensuring the skb-side buffer for memcmp() is valid. No counterexample can be constructed where an attacker-controlled value in this packet directly corrupts peer_cookie_len. This is a classic false positive from taint propagation through a pointer lookup.
l2tp_ip6_recv() — net/l2tp/l2tp_ip6.c FP confidence=high
The function correctly validates the received packet: pskb_may_pull() ensures the first 4 bytes are linear before reading session_id; l2tp_v3_session_get() performs a kernel IDR lookup using session_id as a key and returns a kernel-internal struct or NULL; NULL checks follow both the session lookup and tunnel dereference; l2tp_v3_ensure_opt_in_linear() validates that optional fields fit in the linear region before l2tp_recv_common() is called. The taint propagation from session_id to session->peer_cookie_len is a false positive because session is a kernel-internal object whose fields (peer_cookie_len, peer_cookie) are set by the kernel during session configuration, not derived from the incoming packet's content.
Finding #1 — Category B — cross-function via l2tp_recv_common() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohl() line 155 |
| Taint snippet | session_id = ntohl(*((__be32 *)ptr)); |
| Tainted var | session |
| Call site | line 179 — passes session to l2tp_recv_common() |
| Call snippet | l2tp_recv_common(session, skb, ptr, optr, 0, skb->len); |
| Sink (in callee) | memcmp() line 874 (arg 2, role=size) |
| Sink snippet | if (memcmp(ptr, &session->peer_cookie[0], session->peer_cookie_len)) { |
| Possibly guarded | yes (heuristic) |
Dismissed: The scanner incorrectly propagates taint from session_id (network-supplied) through l2tp_v3_session_get() to the returned session object. However, session is a kernel-internal struct looked up by session_id as a key in a kernel IDR table. The fields session->peer_cookie_len and session->peer_cookie are populated by the kernel during L2TP session setup (not from the packet being processed). peer_cookie_len is bounded by L2TP_COOKIE_SIZE (8 bytes) and is kernel-controlled. No counterexample can be constructed: a remote peer cannot influence peer_cookie_len by choosing a particular session_id value — the worst case is the session is not found (returning NULL, which is checked). This is a textbook false positive of the 'lookup key taint propagation' pattern.
llc_sap_action_send_test_r() — net/llc/llc_s_ac.c FP confidence=high
data_size is genuinely network-supplied (from h_proto), and the subtraction can underflow if h_proto < 3, creating a large u32. However, the flagged sinks at llc_alloc_frame lines 56-57 (skb->protocol and skb->dev assignments) are not tainted pointer dereferences — they operate on the freshly-allocated skb pointer, not on any offset derived from data_size. The NULL check on nskb (line 165) protects against the case where alloc_skb fails due to the large size. The scanner has misidentified normal post-allocation field assignments as tainted pointer dereferences.
Finding #1 — Category E — cross-function via llc_alloc_frame() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 163 |
| Taint snippet | data_size = ntohs(eth_hdr(skb)->h_proto) - 3; |
| Tainted var | data_size |
| Call site | line 164 — passes data_size to llc_alloc_frame() |
| Call snippet | nskb = llc_alloc_frame(NULL, skb->dev, LLC_PDU_TYPE_U, data_size); |
| Pointer deref | data_size-> line 56 |
| Sink snippet | skb->protocol = htons(ETH_P_802_2); |
| Possibly guarded | no |
Dismissed: The sink at line 56 (skb->protocol = htons(ETH_P_802_2)) is a plain assignment to a field of the newly allocated skb. It does not use data_size as a pointer or index — data_size only affects the alloc_skb() size argument. If alloc_skb fails (which it will for absurdly large sizes), the NULL check at line 165 guards against use of nskb. No concrete counterexample exists where data_size causes a dereference at this sink. False positive.
Finding #2 — Category E — cross-function via llc_alloc_frame() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 163 |
| Taint snippet | data_size = ntohs(eth_hdr(skb)->h_proto) - 3; |
| Tainted var | data_size |
| Call site | line 164 — passes data_size to llc_alloc_frame() |
| Call snippet | nskb = llc_alloc_frame(NULL, skb->dev, LLC_PDU_TYPE_U, data_size); |
| Pointer deref | data_size-> line 57 |
| Sink snippet | skb->dev = dev; |
| Possibly guarded | no |
Dismissed: Same analysis as finding #1. The sink at line 57 (skb->dev = dev) is a plain assignment to the freshly allocated skb's dev field. data_size is not used as a pointer or array index at this sink — it only determined the allocation size. The NULL check on nskb protects against failed allocations. False positive.
llc_station_ac_send_test_r() — net/llc/llc_station.c FP confidence=high
The scanner's taint propagation is incorrect: it marks the skb pointer returned by alloc_skb() as tainted because the allocation size argument (data_size) was network-supplied, then flags subsequent field accesses on that pointer as 'tainted pointer dereferences.' In reality, alloc_skb() returns a kernel-allocated address — not a pointer derived from the network value — so skb->protocol and skb->dev accesses are normal and safe. The actual concern in this function is a potential integer underflow (h_proto < 3 → data_size wraps to large u32) followed by alloc_skb getting an excessive or overflowed size; alloc_skb would return NULL in that case, which is already handled. The flagged sinks themselves are not vulnerable.
Finding #1 — Category E — cross-function via llc_alloc_frame() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 77 |
| Taint snippet | data_size = ntohs(eth_hdr(skb)->h_proto) - 3; |
| Tainted var | data_size |
| Call site | line 78 — passes data_size to llc_alloc_frame() |
| Call snippet | nskb = llc_alloc_frame(NULL, skb->dev, LLC_PDU_TYPE_U, data_size); |
| Pointer deref | data_size-> line 56 |
| Sink snippet | skb->protocol = htons(ETH_P_802_2); |
| Possibly guarded | no |
Dismissed: The taint is genuinely network-supplied (Ethernet h_proto field). However, the flagged sink (skb->protocol at line 56) is a field write on a pointer returned by alloc_skb() — a kernel allocator. The scanner incorrectly propagates taint from the size argument through alloc_skb to the returned pointer. The returned skb address is not derived from the network-supplied value; it is a fresh kernel heap allocation. This is a false positive on the specific sink. A real (separate) concern is underflow if h_proto < 3, but alloc_skb failure is already handled by the NULL check.
Finding #2 — Category E — cross-function via llc_alloc_frame() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 77 |
| Taint snippet | data_size = ntohs(eth_hdr(skb)->h_proto) - 3; |
| Tainted var | data_size |
| Call site | line 78 — passes data_size to llc_alloc_frame() |
| Call snippet | nskb = llc_alloc_frame(NULL, skb->dev, LLC_PDU_TYPE_U, data_size); |
| Pointer deref | data_size-> line 57 |
| Sink snippet | skb->dev = dev; |
| Possibly guarded | no |
Dismissed: Same analysis as finding #1. The sink (skb->dev at line 57) is a field write on a kernel-allocated skb pointer, not a pointer derived from the network-supplied data_size. The scanner's taint propagation through alloc_skb's return value is incorrect. False positive on the specific sink classification.
ieee80211_process_addba_request() — net/mac80211/agg-rx.c FP confidence=high
tid is extracted from a 4-bit field (max value 15) which is always a valid index for IEEE80211_NUM_TIDS=16 sized arrays. buf_size is validated and clamped in __ieee80211_start_rx_ba_session() before the loop (lines 347-364 check buf_size > max_buf_size and clamp to sta->sta.max_rx_aggregation_subframes). All flagged accesses are protected by either inherent bit-width constraints or explicit callee-side validation.
Finding #1 — Category C — cross-function via __ieee80211_start_rx_ba_session() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 487 |
| Taint snippet | capab = le16_to_cpu(mgmt->u.action.addba_req.capab); |
| Tainted var | tid |
| Call site | line 500 — passes tid to __ieee80211_start_rx_ba_session() |
| Call snippet | __ieee80211_start_rx_ba_session(sta, dialog_token, timeout, |
| Subscript (in callee) | [] line 371 |
| Sink snippet | if (sta->ampdu_mlme.tid_rx_token[tid] == dialog_token) { |
| Possibly guarded | yes (heuristic) |
Dismissed: tid is masked by IEEE80211_ADDBA_PARAM_TID_MASK and shifted to give a 4-bit value (0-15). tid_rx_token[] has IEEE80211_NUM_TIDS=16 entries. No counterexample exists: all 4-bit values are valid indices.
Finding #2 — Category C — cross-function via __ieee80211_start_rx_ba_session() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 487 |
| Taint snippet | capab = le16_to_cpu(mgmt->u.action.addba_req.capab); |
| Tainted var | tid |
| Call site | line 500 — passes tid to __ieee80211_start_rx_ba_session() |
| Call snippet | __ieee80211_start_rx_ba_session(sta, dialog_token, timeout, |
| Subscript (in callee) | [] line 383 |
| Sink snippet | tid_rx = rcu_dereference(sta->ampdu_mlme.tid_rx[tid]); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #1: tid is a 4-bit value (0-15), valid for all 16-element arrays. No counterexample possible.
Finding #3 — Category C — cross-function via __ieee80211_start_rx_ba_session() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 487 |
| Taint snippet | capab = le16_to_cpu(mgmt->u.action.addba_req.capab); |
| Tainted var | tid |
| Call site | line 500 — passes tid to __ieee80211_start_rx_ba_session() |
| Call snippet | __ieee80211_start_rx_ba_session(sta, dialog_token, timeout, |
| Subscript (in callee) | [] line 452 |
| Sink snippet | rcu_assign_pointer(sta->ampdu_mlme.tid_rx[tid], tid_agg_rx); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #1: tid is a 4-bit value (0-15), valid for all 16-element arrays. No counterexample possible.
Finding #4 — Category C — cross-function via __ieee80211_start_rx_ba_session() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 487 |
| Taint snippet | capab = le16_to_cpu(mgmt->u.action.addba_req.capab); |
| Tainted var | tid |
| Call site | line 500 — passes tid to __ieee80211_start_rx_ba_session() |
| Call snippet | __ieee80211_start_rx_ba_session(sta, dialog_token, timeout, |
| Subscript (in callee) | [] line 463 |
| Sink snippet | sta->ampdu_mlme.tid_rx_token[tid] = dialog_token; |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #1: tid is a 4-bit value (0-15), valid for all 16-element arrays. No counterexample possible.
Finding #5 — Category F — cross-function via __ieee80211_start_rx_ba_session() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le16_to_cpu() line 487 |
| Taint snippet | capab = le16_to_cpu(mgmt->u.action.addba_req.capab); |
| Tainted var | buf_size |
| Call site | line 500 — passes buf_size to __ieee80211_start_rx_ba_session() |
| Call snippet | __ieee80211_start_rx_ba_session(sta, dialog_token, timeout, |
| Loop | for_loop line 427 |
| Sink snippet | for (i = 0; i < buf_size; i++) |
| Possibly guarded | yes (heuristic) |
Dismissed: buf_size is validated in __ieee80211_start_rx_ba_session() at lines 347-364: rejected if > max_buf_size, then clamped to sta->sta.max_rx_aggregation_subframes. The loop at line 427 iterates over an array allocated with buf_size elements (line 424: kzalloc(sizeof(*tid_agg_rx->reorder_buf) * buf_size)), so the loop bound equals the allocation size after clamping. No counterexample possible.
ieee80211_process_addba_resp() — net/mac80211/agg-tx.c FP confidence=high
The tid value is extracted from a 4-bit bitfield (IEEE80211_ADDBA_PARAM_TID_MASK) via u16_get_bits(), constraining it to the range [0,15]. All arrays indexed by tid (txq[], addba_req_num[], tid_start_tx[]) are sized IEEE80211_NUM_TIDS=16, so indices 0-15 are all in bounds. No counterexample exists where a valid 4-bit tid could produce an OOB access. The scanner correctly identifies that no explicit bounds check is present but fails to account for the bit-mask constraint that implicitly bounds the value.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 989 |
| Taint snippet | capab = le16_to_cpu(mgmt->u.action.addba_resp.capab); |
| Tainted var | tid |
| Subscript | [] line 1002 |
| Sink snippet | txq = sta->sta.txq[tid]; |
| Possibly guarded | no |
Dismissed: tid is extracted via u16_get_bits(capab, IEEE80211_ADDBA_PARAM_TID_MASK) where the mask selects a 4-bit field, limiting tid to [0,15]. sta->sta.txq[] is sized IEEE80211_NUM_TIDS+1 or similar (at least 16 entries). No counterexample exists: a 4-bit field cannot produce a value >= 16.
Finding #2 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 989 |
| Taint snippet | capab = le16_to_cpu(mgmt->u.action.addba_resp.capab); |
| Tainted var | tid |
| Subscript | [] line 1054 |
| Sink snippet | sta->ampdu_mlme.addba_req_num[tid] = 0; |
| Possibly guarded | no |
Dismissed: Same tid value (4-bit masked) indexes addba_req_num[IEEE80211_NUM_TIDS] which has 16 elements. Values 0-15 are all valid. No counterexample possible.
Finding #3 — Category C — cross-function via __ieee80211_stop_tx_ba_session() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 989 |
| Taint snippet | capab = le16_to_cpu(mgmt->u.action.addba_resp.capab); |
| Tainted var | tid |
| Call site | line 1066 — passes tid to __ieee80211_stop_tx_ba_session() |
| Call snippet | __ieee80211_stop_tx_ba_session(sta, tid, AGG_STOP_DECLINED); |
| Subscript (in callee) | [] line 325 |
| Sink snippet | tid_tx = sta->ampdu_mlme.tid_start_tx[tid]; |
| Possibly guarded | no |
Dismissed: tid passed to __ieee80211_stop_tx_ba_session() is still the 4-bit masked value [0,15]. tid_start_tx[tid] at line 325 indexes a 16-element array. No OOB possible.
Finding #4 — Category C — cross-function via __ieee80211_stop_tx_ba_session() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 989 |
| Taint snippet | capab = le16_to_cpu(mgmt->u.action.addba_resp.capab); |
| Tainted var | tid |
| Call site | line 1066 — passes tid to __ieee80211_stop_tx_ba_session() |
| Call snippet | __ieee80211_stop_tx_ba_session(sta, tid, AGG_STOP_DECLINED); |
| Subscript (in callee) | [] line 327 |
| Sink snippet | sta->ampdu_mlme.tid_start_tx[tid] = NULL; |
| Possibly guarded | no |
Dismissed: Same as finding #3 — line 327 sets tid_start_tx[tid]=NULL, also within the 16-element array bounds guaranteed by the 4-bit mask on tid.
ieee80211_rx_uhr_link_reconfig_req() — net/mac80211/ap.c FP confidence=high
The function has good validation discipline: ieee80211_mle_reconf_sta_prof_size_ok() validates the per-STA profile structure before any field accesses; link_id is masked with IEEE80211_MLE_STA_RECONF_CONTROL_LINK_ID (a 4-bit field yielding values 0-15) before assignment to u8, causing no actual truncation loss; and the explicit bounds check `link_id >= IEEE80211_MLD_MAX_NUM_LINKS` at line 241 guards both array subscript accesses at lines 244 and 255. No counterexample can be constructed that passes the bounds check yet causes OOB.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 239 |
| Taint snippet | link_id = control & IEEE80211_MLE_STA_RECONF_CONTROL_LINK_ID; |
| Tainted var | link_id |
| Truncation | line 239: 16 → 8-bit u8 |
| Sink snippet | link_id = control & IEEE80211_MLE_STA_RECONF_CONTROL_LINK_ID; |
| Possibly guarded | no |
Dismissed: IEEE80211_MLE_STA_RECONF_CONTROL_LINK_ID is a bitmask for the link ID field in the per-STA profile control word. Link IDs in 802.11 MLE are 4-bit values (0-15), so the mask limits the result to at most 15, which fits in a u8 without any truncation loss. The subsequent bounds check `link_id >= IEEE80211_MLD_MAX_NUM_LINKS` (which is 15) further confirms the value is well-controlled. No counterexample exists where masking followed by bounds-check fails.
Finding #2 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 238 |
| Taint snippet | control = le16_to_cpu(prof->control); |
| Tainted var | link_id |
| Subscript | [] line 244 |
| Sink snippet | link = sdata_dereference(sdata->link[link_id], sdata); |
| Possibly guarded | yes (heuristic) |
Dismissed: The explicit check `if (link_id >= IEEE80211_MLD_MAX_NUM_LINKS) return;` at line 241-242 occurs before the array access at line 244. sdata->link[] is sized IEEE80211_MLD_MAX_NUM_LINKS, so any link_id passing the check is a valid array index. No counterexample can be constructed: any value >= IEEE80211_MLD_MAX_NUM_LINKS causes early return; any value < IEEE80211_MLD_MAX_NUM_LINKS is in-bounds.
Finding #3 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 238 |
| Taint snippet | control = le16_to_cpu(prof->control); |
| Tainted var | link_id |
| Subscript | [] line 255 |
| Sink snippet | link_sta = sdata_dereference(sta->link[link_id], sdata); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same link_id value and same bounds check as finding #2. The check at line 241-242 guards this access at line 255 as well. sta->link[] is also sized IEEE80211_MLD_MAX_NUM_LINKS. No counterexample exists. False positive.
ieee80211_eht_cap_ie_to_sta_eht_cap() — net/mac80211/eht.c FP confidence=medium
The function has reasonable bounds checking discipline. The truncation of eht_ppe_size from a value derived from a u16 input is a theoretical concern, but the truncation makes the memcpy copy FEWER bytes than intended (never more), so it cannot cause an OOB write to the destination. The destination guard at line 54 checks the already-truncated u8 value. An adversarial crafted value that truncates would cause incorrect (smaller) copying but not memory corruption. The source buffer is validated via eht_cap_len at line 58 after computing eht_total_size which includes eht_ppe_size.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | get_unaligned_le16() line 48 |
| Taint snippet | eht_ppe_size = |
| Tainted var | eht_ppe_size |
| Truncation | line 48: 16 → 8-bit u8 |
| Sink snippet | eht_ppe_size = |
| Possibly guarded | no |
Dismissed: The truncation from u16 to u8 happens BEFORE the bounds check at line 54. However, truncation can only make the value smaller (e.g., 256→0, 257→1), meaning the memcpy would copy fewer bytes than the actual PPE size — a correctness bug but not an OOB write. No counterexample exists where the truncated value causes eht_ppe_size to exceed sizeof(eht_ppe_thres) when the true value would not, because truncation always reduces the value. The risk is the opposite: a too-small copy silently succeeds.
Finding #2 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | get_unaligned_le16() line 47 |
| Taint snippet | eht_ppe_hdr = get_unaligned_le16(eht_cap_ie_elem->optional + mcs_nss_size); |
| Tainted var | eht_ppe_size |
| Sink | memcpy() line 71 (arg 2, role=size) |
| Sink snippet | memcpy(eht_cap->eht_ppe_thres, |
| Possibly guarded | yes (heuristic) |
Dismissed: Destination check: line 54 guards eht_ppe_size <= sizeof(eht_ppe_thres). Source check: line 58 guards eht_cap_len >= eht_total_size which includes eht_ppe_size. Both checks use the (possibly truncated) u8 value. Since truncation only makes the value smaller, the destination check cannot be bypassed to allow OOB write. Cannot construct a counterexample where truncated eht_ppe_size exceeds sizeof(eht_ppe_thres) while original would not — truncation reduces the value.
ieee80211_process_delba() — net/mac80211/ht.c FP confidence=high
tid is derived from a 4-bit bitfield (bits 15:12 of params, masked with IEEE80211_DELBA_PARAM_TID_MASK then shifted right 12), constraining it to [0, 15]. The arrays tid_rx[], tid_start_tx[], and agg_session_valid are all IEEE80211_NUM_TIDS (16) elements wide, so indices 0-15 are all valid. The static analyzer propagates taint through the le16_to_cpu() call but does not track the value-range constraint imposed by the subsequent mask-and-shift operation. No counterexample can be constructed because the mask operation makes tid > 15 impossible.
Finding #1 — Category C — cross-function via __ieee80211_stop_rx_ba_session() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 466 |
| Taint snippet | params = le16_to_cpu(mgmt->u.action.delba.params); |
| Tainted var | tid |
| Call site | line 476 — passes tid to __ieee80211_stop_rx_ba_session() |
| Call snippet | __ieee80211_stop_rx_ba_session(sta, tid, WLAN_BACK_INITIATOR, 0, |
| Subscript (in callee) | [] line 72 |
| Sink snippet | tid_rx = rcu_dereference_protected(sta->ampdu_mlme.tid_rx[tid], |
| Possibly guarded | no |
Dismissed: tid = (params & IEEE80211_DELBA_PARAM_TID_MASK) >> 12 constrains tid to [0,15]. sta->ampdu_mlme.tid_rx[] has IEEE80211_NUM_TIDS=16 entries. No OOB possible; cannot construct a counterexample.
Finding #2 — Category C — cross-function via __ieee80211_stop_rx_ba_session() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 466 |
| Taint snippet | params = le16_to_cpu(mgmt->u.action.delba.params); |
| Tainted var | tid |
| Call site | line 476 — passes tid to __ieee80211_stop_rx_ba_session() |
| Call snippet | __ieee80211_stop_rx_ba_session(sta, tid, WLAN_BACK_INITIATOR, 0, |
| Subscript (in callee) | [] line 78 |
| Sink snippet | RCU_INIT_POINTER(sta->ampdu_mlme.tid_rx[tid], NULL); |
| Possibly guarded | no |
Dismissed: Same as finding 1: tid is 4-bit masked, array is 16 elements. RCU_INIT_POINTER access at tid_rx[tid] is safe for all possible tid values.
Finding #3 — Category C — cross-function via __ieee80211_stop_tx_ba_session() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 466 |
| Taint snippet | params = le16_to_cpu(mgmt->u.action.delba.params); |
| Tainted var | tid |
| Call site | line 479 — passes tid to __ieee80211_stop_tx_ba_session() |
| Call snippet | __ieee80211_stop_tx_ba_session(sta, tid, AGG_STOP_PEER_REQUEST); |
| Subscript (in callee) | [] line 325 |
| Sink snippet | tid_tx = sta->ampdu_mlme.tid_start_tx[tid]; |
| Possibly guarded | no |
Dismissed: tid is constrained to [0,15] by the mask IEEE80211_DELBA_PARAM_TID_MASK >> 12. tid_start_tx[] is IEEE80211_NUM_TIDS=16 elements. No counterexample possible.
Finding #4 — Category C — cross-function via __ieee80211_stop_tx_ba_session() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 466 |
| Taint snippet | params = le16_to_cpu(mgmt->u.action.delba.params); |
| Tainted var | tid |
| Call site | line 479 — passes tid to __ieee80211_stop_tx_ba_session() |
| Call snippet | __ieee80211_stop_tx_ba_session(sta, tid, AGG_STOP_PEER_REQUEST); |
| Subscript (in callee) | [] line 327 |
| Sink snippet | sta->ampdu_mlme.tid_start_tx[tid] = NULL; |
| Possibly guarded | no |
Dismissed: Same as finding 3: write to tid_start_tx[tid] where tid in [0,15] and array has 16 entries. False positive due to analyzer not tracking bitmask value constraints.
ieee80211_mesh_rx_bcn_presp() — net/mac80211/mesh.c FP confidence=high
The scanner incorrectly propagates taint from `type = le16_to_cpu(mgmt->frame_control) & IEEE80211_FCTL_TYPE` through the call to `ieee802_11_parse_elems(..., type, ...)` to the returned `elems` pointer. However, `elems` is a kernel-allocated struct (`struct ieee802_11_elems`) populated by the element parser, which validates all element lengths and offsets during parsing. The `type` parameter only affects parsing mode/behavior, not the memory safety of the returned struct. Similarly, `channel` is obtained from `ieee80211_get_channel()`, a kernel-internal lookup unrelated to packet offsets. All flagged dereferences are on kernel-internal structures, not raw packet pointers. The `baselen > len` check at line 1464 ensures the element region is valid before parsing.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 1449 |
| Taint snippet | u16 type = le16_to_cpu(mgmt->frame_control) & IEEE80211_FCTL_TYPE; |
| Tainted var | elems |
| Pointer deref | elems->mesh_id line 1473 |
| Sink snippet | if ((!elems->mesh_id || !elems->mesh_config) || |
| Possibly guarded | no |
Dismissed: elems is a kernel-allocated struct returned by ieee802_11_parse_elems(). The scanner incorrectly propagates taint from `type` (passed as a parameter) to the return value `elems`. The `elems->mesh_id` field is a pointer set by the element parser with internal bounds validation. This is a false positive due to incorrect taint propagation through a function call return value.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 1449 |
| Taint snippet | u16 type = le16_to_cpu(mgmt->frame_control) & IEEE80211_FCTL_TYPE; |
| Tainted var | elems |
| Pointer deref | elems->rsn line 1474 |
| Sink snippet | (elems->rsn && sdata->u.mesh.security == IEEE80211_MESH_SEC_NONE) || |
| Possibly guarded | no |
Dismissed: Same false positive pattern as finding #1. elems->rsn is a pointer field set by the kernel element parser, not a raw packet offset. Taint from `type` should not propagate to the return struct of ieee802_11_parse_elems().
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 1449 |
| Taint snippet | u16 type = le16_to_cpu(mgmt->frame_control) & IEEE80211_FCTL_TYPE; |
| Tainted var | elems |
| Pointer deref | elems->rsn line 1475 |
| Sink snippet | (!elems->rsn && sdata->u.mesh.security != IEEE80211_MESH_SEC_NONE)) |
| Possibly guarded | no |
Dismissed: Same false positive as finding #2. elems->rsn on line 1475 is the same field, same struct, same analysis applies.
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le16_to_cpu() line 1449 |
| Taint snippet | u16 type = le16_to_cpu(mgmt->frame_control) & IEEE80211_FCTL_TYPE; |
| Tainted var | elems |
| Pointer deref | elems->ds_params line 1478 |
| Sink snippet | if (elems->ds_params) |
| Possibly guarded | no |
Dismissed: elems->ds_params is a pointer field in the kernel-allocated elems struct. The null check `if (elems->ds_params)` at line 1478 guards the access at line 1479. The element parser validates the DS params element length before setting this pointer. 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 1449 |
| Taint snippet | u16 type = le16_to_cpu(mgmt->frame_control) & IEEE80211_FCTL_TYPE; |
| Tainted var | elems |
| Pointer deref | elems->ds_params line 1479 |
| Sink snippet | freq = ieee80211_channel_to_frequency(elems->ds_params[0], band); |
| Possibly guarded | no |
Dismissed: elems->ds_params[0] is accessed after the null check at line 1478. The DS Parameters element is exactly 1 byte in 802.11 (the channel number), and the element parser validates this before setting the pointer. 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 1449 |
| Taint snippet | u16 type = le16_to_cpu(mgmt->frame_control) & IEEE80211_FCTL_TYPE; |
| Tainted var | channel |
| Pointer deref | channel->flags line 1485 |
| Sink snippet | if (!channel || channel->flags & IEEE80211_CHAN_DISABLED) |
| Possibly guarded | no |
Dismissed: channel is the return value of ieee80211_get_channel(), a kernel-internal function that looks up a channel in the wiphy's channel table. It has no relationship to packet offsets. The null check `!channel` at line 1485 guards the `channel->flags` access. This is a completely spurious taint path; the scanner has incorrectly traced taint from `type` through `freq` computation to `channel`. 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 1449 |
| Taint snippet | u16 type = le16_to_cpu(mgmt->frame_control) & IEEE80211_FCTL_TYPE; |
| Tainted var | elems |
| Pointer deref | elems->mesh_config line 1506 |
| Sink snippet | elems->mesh_config, rx_status); |
| Possibly guarded | no |
Dismissed: elems->mesh_config is accessed at line 1506. It was already verified non-null at line 1473 (`!elems->mesh_config` check). The field is set by the kernel element parser with internal bounds validation. False positive.
Finding #8 — Category B — cross-function via mesh_matches_local() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 1449 |
| Taint snippet | u16 type = le16_to_cpu(mgmt->frame_control) & IEEE80211_FCTL_TYPE; |
| Tainted var | elems |
| Call site | line 1488 — passes elems to mesh_matches_local() |
| Call snippet | if (mesh_matches_local(sdata, elems)) { |
| Sink (in callee) | memcmp() line 85 (arg 2, role=size) |
| Sink snippet | memcmp(ifmsh->mesh_id, ie->mesh_id, ie->mesh_id_len) == 0 && |
| Possibly guarded | yes (heuristic) |
Dismissed: ie->mesh_id_len is set by the element parser (ieee802_11_parse_elems_full) which validates that the mesh_id element fits within the received buffer before recording its length. Therefore mesh_id_len is bounded by the element's actual in-packet length. The memcmp() in mesh_matches_local() uses this validated length to compare against ifmsh->mesh_id (which has its own length ifmsh->mesh_id_len — the equality check `ifmsh->mesh_id_len == ie->mesh_id_len` at line 84 ensures both lengths match before memcmp). No counterexample exists because the parser-validated length cannot exceed the element's true packet bounds. False positive.
mesh_rmc_check() — net/mac80211/mesh.c FP confidence=high
The index 'idx' is computed as le32_to_cpu(mesh_hdr->seqnum) & rmc->idx_mask, where rmc->idx_mask is RMC_BUCKETS-1 (255 = 0xFF). This masking operation ensures the result always fits in a u8 and is always a valid index into the rmc->bucket[] array (sized RMC_BUCKETS=256). No counterexample exists that would pass the mask and cause OOB access. All three findings are false positives stemming from the scanner not recognizing that the idx_mask is a power-of-two-minus-one kernel-controlled bitmask that bounds the index.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le32_to_cpu() line 241 |
| Taint snippet | idx = le32_to_cpu(mesh_hdr->seqnum) & rmc->idx_mask; |
| Tainted var | idx |
| Truncation | line 241: 32 → 8-bit u8 |
| Sink snippet | idx = le32_to_cpu(mesh_hdr->seqnum) & rmc->idx_mask; |
| Possibly guarded | no |
Dismissed: The scanner flags the 32-to-8 bit truncation, but explicitly says to mark as false positive when masking is used. The expression '& rmc->idx_mask' where idx_mask = RMC_BUCKETS-1 = 0xFF constrains the result to [0,255] before truncation to u8, so no information is lost. No counterexample can be constructed.
Finding #2 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le32_to_cpu() line 241 |
| Taint snippet | idx = le32_to_cpu(mesh_hdr->seqnum) & rmc->idx_mask; |
| Tainted var | idx |
| Subscript | [] line 242 |
| Sink snippet | hlist_for_each_entry_safe(p, n, &rmc->bucket[idx], list) { |
| Possibly guarded | no |
Dismissed: idx is bounded to [0, RMC_BUCKETS-1] by the '& rmc->idx_mask' operation (idx_mask = RMC_BUCKETS-1). The bucket array has RMC_BUCKETS entries. No OOB is possible. Cannot construct a counterexample: any seqnum masked with 0xFF gives a value in [0,255] which is valid for a 256-entry array.
Finding #3 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le32_to_cpu() line 241 |
| Taint snippet | idx = le32_to_cpu(mesh_hdr->seqnum) & rmc->idx_mask; |
| Tainted var | idx |
| Subscript | [] line 260 |
| Sink snippet | hlist_add_head(&p->list, &rmc->bucket[idx]); |
| Possibly guarded | no |
Dismissed: Same analysis as finding #2 applies to the second use of idx at line 260. The mask applied at computation time fully constrains the index. False positive.
mesh_process_plink_frame() — net/mac80211/mesh_plink.c FP confidence=high
The function has solid validation discipline: ie_len is validated against exact expected values for each frame type before the peering IEs are parsed; server-supplied values (plid, llid) are u16 reads used only in limited ways. The static analyzer over-propagates taint through mesh_plink_get_event(), treating its enum return value as tainted because llid was an input. In reality, mesh_plink_get_event() returns compile-time enum constants, not arbitrary derivations of the network-supplied llid. Additionally, the PLINK_UNDEFINED guard at line 1199 prevents mesh_plink_fsm() from being called with invalid event values.
Finding #1 — Category C — cross-function via mesh_plink_fsm() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | get_unaligned_le16() line 1168 |
| Taint snippet | llid = get_unaligned_le16(PLINK_GET_PLID(elems->peering)); |
| Tainted var | event |
| Call site | line 1212 — passes event to mesh_plink_fsm() |
| Call snippet | changed |= mesh_plink_fsm(sdata, sta, event); |
| Subscript (in callee) | [] line 878 |
| Sink snippet | mplstates[sta->mesh->plink_state], mplevents[event]); |
| Possibly guarded | yes (heuristic) |
Dismissed: The taint propagation from llid (network-supplied u16) to 'event' is incorrect — mesh_plink_get_event() returns a fixed enum plink_event value (OPN_ACPT, CNF_ACPT, CLS_ACPT, OPN_RJCT, CNF_RJCT, PLINK_UNDEFINED), not an arbitrary value derived from llid. The guard at line 1199 ensures PLINK_UNDEFINED causes an early return before mesh_plink_fsm() is called. No counterexample can be constructed: any valid enum value from mesh_plink_get_event() that passes the guards is a defined constant that indexes safely into mplevents[]. This is an over-taint false positive.
ieee80211_assoc_config_link() — net/mac80211/mlme.c FP confidence=high
The taint source (le16_to_cpu() at line 5862) reads mgmt->frame_control into parse_params.type. The scanner incorrectly propagates taint from parse_params through ieee802_11_parse_elems_full() to elems. In reality: (1) elems is validated output from the parser, not raw server data; (2) the parser validates all element pointers within the input buffer; (3) ieee80211_eht_cap_ie_to_sta_eht_cap() internally validates eht_cap_len against all computed sizes (mcs_nss_size, eht_ppe_size) before any memcpy or array subscript; (4) the memcmp sink in ieee80211_config_bw() uses sizeof(elems->tpe) which is compile-time constant. All findings are false positives.
Finding #1 — Category B — cross-function via ieee80211_config_bw() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 5862 |
| Taint snippet | struct ieee80211_elems_parse_params parse_params = { |
| Tainted var | elems |
| Call site | line 6045 — passes elems to ieee80211_config_bw() |
| Call snippet | if (ieee80211_config_bw(link, elems, |
| Sink (in callee) | memcmp() line 1690 (arg 2, role=size) |
| Sink snippet | if (memcmp(&link->conf->tpe, &elems->tpe, sizeof(elems->tpe))) { |
| Possibly guarded | no |
Dismissed: The memcmp at line 1690 uses sizeof(elems->tpe) as the size argument - this is a compile-time constant sizeof, not a server-supplied value. The scanner misidentified this as a size sink because it followed taint through elems, but sizeof() of a struct field is always a compile-time constant regardless of taint. No counterexample exists.
Finding #2 — Category H — cross-function via ieee80211_eht_cap_ie_to_sta_eht_cap() — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 5862 |
| Taint snippet | struct ieee80211_elems_parse_params parse_params = { |
| Tainted var | elems |
| Call site | line 6127 — passes elems to ieee80211_eht_cap_ie_to_sta_eht_cap() |
| Call snippet | ieee80211_eht_cap_ie_to_sta_eht_cap(sdata, sband, |
| Truncation | line 48: None → None-bit |
| Sink snippet | eht_ppe_size = |
| Possibly guarded | no |
Dismissed: eht_ppe_size is computed from get_unaligned_le16() of eht_cap_ie_elem->optional data, but is bounded by: (1) check at line 44 that eht_cap_len >= eht_total_size + sizeof(u16) before reading the PPE header; (2) check at line 54 that eht_ppe_size <= sizeof(eht_cap->eht_ppe_thres); (3) check at line 58 that eht_cap_len >= eht_total_size. The narrowing assignment to u8 eht_ppe_size is safe because line 54 ensures it fits. Cannot construct counterexample that passes all guards.
Finding #3 — Category B — cross-function via ieee80211_eht_cap_ie_to_sta_eht_cap() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 5862 |
| Taint snippet | struct ieee80211_elems_parse_params parse_params = { |
| Tainted var | elems |
| Call site | line 6127 — passes elems to ieee80211_eht_cap_ie_to_sta_eht_cap() |
| Call snippet | ieee80211_eht_cap_ie_to_sta_eht_cap(sdata, sband, |
| Sink (in callee) | memcpy() line 68 (arg 2, role=size) |
| Sink snippet | memcpy(&eht_cap->eht_mcs_nss_supp, pos, mcs_nss_size); |
| Possibly guarded | no |
Dismissed: mcs_nss_size is derived from ieee80211_eht_mcs_nss_size() which computes a size based on capability bits. The check at line 58 (eht_cap_len < eht_total_size where eht_total_size = sizeof(eht_cap_elem) + mcs_nss_size) ensures mcs_nss_size <= eht_cap_len - sizeof(eht_cap_elem). The destination eht_mcs_nss_supp is zeroed and sized as the union, and memcpy copies at most mcs_nss_size bytes which is bounded. No counterexample exists.
Finding #4 — Category B — cross-function via ieee80211_eht_cap_ie_to_sta_eht_cap() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 5862 |
| Taint snippet | struct ieee80211_elems_parse_params parse_params = { |
| Tainted var | elems |
| Call site | line 6127 — passes elems to ieee80211_eht_cap_ie_to_sta_eht_cap() |
| Call snippet | ieee80211_eht_cap_ie_to_sta_eht_cap(sdata, sband, |
| Sink (in callee) | memcpy() line 71 (arg 2, role=size) |
| Sink snippet | memcpy(eht_cap->eht_ppe_thres, |
| Possibly guarded | yes (heuristic) |
Dismissed: eht_ppe_size at line 71 memcpy is guarded by line 54 check (eht_ppe_size <= sizeof(eht_cap->eht_ppe_thres)) and the eht_cap_len >= eht_total_size check at line 58. The destination eht_ppe_thres has size sizeof(eht_cap->eht_ppe_thres) and eht_ppe_size is verified <= that size. No counterexample exists.
Finding #5 — Category C — cross-function via ieee80211_eht_cap_ie_to_sta_eht_cap() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 5862 |
| Taint snippet | struct ieee80211_elems_parse_params parse_params = { |
| Tainted var | elems |
| Call site | line 6127 — passes elems to ieee80211_eht_cap_ie_to_sta_eht_cap() |
| Call snippet | ieee80211_eht_cap_ie_to_sta_eht_cap(sdata, sband, |
| Subscript (in callee) | [] line 72 |
| Sink snippet | &eht_cap_ie_elem->optional[mcs_nss_size], |
| Possibly guarded | no |
Dismissed: The array subscript eht_cap_ie_elem->optional[mcs_nss_size] at line 72 is reached only when eht_ppe_size > 0 (line 70). The check at line 58 ensures eht_cap_len >= sizeof(eht_cap_elem) + mcs_nss_size + eht_ppe_size, which means the optional[] array (which immediately follows the fixed struct) has at least mcs_nss_size + eht_ppe_size bytes available. The subscript access is within bounds. No counterexample exists.
ieee80211_max_rx_chains() — net/mac80211/mlme.c FP confidence=high
Both findings are false positives. The scanner correctly identified le16_to_cpu() as a taint source but missed that the bitwise mask '& 3' applied before assignment to u8 limits the value to [0,3], making truncation impossible. The values are used only for comparison against IEEE80211_VHT_MCS_NOT_SUPPORTED (==3) and for updating a chains counter bounded by the loop index i in [0,7]. The function also properly validates datalen before accessing he_mcs_nss_supp fields.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 6429 |
| Taint snippet | u8 mcs_80 = mcs_80_map >> (2 * i) & 3; |
| Tainted var | mcs_80 |
| Truncation | line 6429: 16 → 8-bit u8 |
| Sink snippet | u8 mcs_80 = mcs_80_map >> (2 * i) & 3; |
| Possibly guarded | no |
Dismissed: The expression '(mcs_80_map >> (2 * i)) & 3' applies a bitmask of 3 (0b11) before assignment to u8, limiting the value to [0,3]. No truncation loss is possible regardless of the 16-bit input. Counterexample test: even with mcs_80_map=0xFFFF and i=0, the result is 0xFFFF & 3 = 3, which fits in u8. No counterexample can be constructed.
Finding #2 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 6445 |
| Taint snippet | u8 mcs_160 = mcs_160_map >> (2 * i) & 3; |
| Tainted var | mcs_160 |
| Truncation | line 6445: 16 → 8-bit u8 |
| Sink snippet | u8 mcs_160 = mcs_160_map >> (2 * i) & 3; |
| Possibly guarded | no |
Dismissed: Identical pattern to finding #1: '(mcs_160_map >> (2 * i)) & 3' masks to 2 bits before u8 assignment. The & 3 mask guarantees the value is in [0,3], which trivially fits in u8 with no information loss. No counterexample can be constructed.
ieee80211_ml_epcs() — net/mac80211/mlme.c FP confidence=high
The function has proper validation: (1) link_id is masked to 4 bits before truncation to u8, making the truncation safe; (2) link_id is bounds-checked against IEEE80211_MLD_MAX_NUM_LINKS before array indexing; (3) cfg80211_defragment_element validates the element fits within the buffer; (4) ieee802_11_parse_elems validates all elements in the defragmented payload and returns a locally-allocated struct — link_elems fields are set by the kernel parser, not directly derived from untrusted offsets.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | get_unaligned_le16() line 11666 |
| Taint snippet | link_id = control & IEEE80211_MLE_STA_EPCS_CONTROL_LINK_ID; |
| Tainted var | link_id |
| Truncation | line 11666: 16 → 8-bit u8 |
| Sink snippet | link_id = control & IEEE80211_MLE_STA_EPCS_CONTROL_LINK_ID; |
| Possibly guarded | no |
Dismissed: IEEE80211_MLE_STA_EPCS_CONTROL_LINK_ID masks to 4 bits (0x000F), so the masked value is at most 15, which trivially fits in u8. No counterexample exists: any u16 AND 0xF <= 15 < 256. Truncation is safe.
Finding #2 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | get_unaligned_le16() line 11665 |
| Taint snippet | control = get_unaligned_le16(pos); |
| Tainted var | link_id |
| Subscript | [] line 11671 |
| Sink snippet | link = sdata_dereference(sdata->link[link_id], sdata); |
| Possibly guarded | yes (heuristic) |
Dismissed: link_id is bounded to [0, IEEE80211_MLD_MAX_NUM_LINKS-1] by the check at line 11668 before the array subscript at line 11671. The mask ensures link_id <= 15, and the bounds check ensures link_id < IEEE80211_MLD_MAX_NUM_LINKS. No counterexample: any value passing the guard is a valid index.
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | get_unaligned_le16() line 11665 |
| Taint snippet | control = get_unaligned_le16(pos); |
| Tainted var | link_elems |
| Pointer deref | link_elems->wmm_param line 11693 |
| Sink snippet | link_elems->wmm_param, |
| Possibly guarded | no |
Dismissed: link_elems is a kernel-allocated struct returned by ieee802_11_parse_elems(), which internally validates all element bounds. The wmm_param field is a pointer set by the parser to a validated location within the parsed buffer, not a raw server-supplied offset. The taint chain through the parser is a false positive.
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | get_unaligned_le16() line 11665 |
| Taint snippet | control = get_unaligned_le16(pos); |
| Tainted var | link_elems |
| Pointer deref | link_elems->wmm_param_len line 11694 |
| Sink snippet | link_elems->wmm_param_len, |
| Possibly guarded | no |
Dismissed: Same reasoning as finding #3: link_elems->wmm_param_len is populated by the element parser with validated length, not a raw server value used without bounds checking.
Finding #5 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | get_unaligned_le16() line 11665 |
| Taint snippet | control = get_unaligned_le16(pos); |
| Tainted var | link_elems |
| Pointer deref | link_elems->mu_edca_param_set line 11695 |
| Sink snippet | link_elems->mu_edca_param_set)) |
| Possibly guarded | no |
Dismissed: Same reasoning as findings #3 and #4: link_elems->mu_edca_param_set is set by the kernel element parser from validated buffer contents. The scanner's taint propagation through ieee802_11_parse_elems is a false positive.
ieee80211_ml_reconfiguration() — net/mac80211/mlme.c FP confidence=high
The code has proper bounds validation. link_id is masked with IEEE80211_MLE_STA_RECONF_CONTROL_LINK_ID (a small bitmask, 4 bits for link IDs 0-15) and then checked against IEEE80211_MLD_MAX_NUM_LINKS at line 7666 before any array access in the first loop. In the second loop, for_each_set_bit() with IEEE80211_MLD_MAX_NUM_LINKS as the upper bound guarantees link_id is within range, and removed_links can only contain bits that passed the first loop's bounds check. All findings are false positives.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 7664 |
| Taint snippet | link_id = control & IEEE80211_MLE_STA_RECONF_CONTROL_LINK_ID; |
| Tainted var | link_id |
| Truncation | line 7664: 16 → 8-bit u8 |
| Sink snippet | link_id = control & IEEE80211_MLE_STA_RECONF_CONTROL_LINK_ID; |
| Possibly guarded | no |
Dismissed: IEEE80211_MLE_STA_RECONF_CONTROL_LINK_ID is a bitmask for a 4-bit link ID field (values 0-15), which fits within u8. The masking operation (& mask) before assignment to u8 prevents any meaningful truncation. Additionally, the subsequent check at line 7666 against IEEE80211_MLD_MAX_NUM_LINKS (which is 15) makes this safe. Cannot construct counterexample where truncation causes a different value than without truncation given the mask is ≤ 0x0F.
Finding #2 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 7663 |
| Taint snippet | control = le16_to_cpu(prof->control); |
| Tainted var | link_id |
| Subscript | [] line 7682 |
| Sink snippet | link_removal_timeout[link_id] = get_unaligned_le16(pos); |
| Possibly guarded | yes (heuristic) |
Dismissed: Line 7666 explicitly checks `if (link_id >= IEEE80211_MLD_MAX_NUM_LINKS) continue;` before reaching line 7682. link_removal_timeout is declared with size IEEE80211_MLD_MAX_NUM_LINKS. Cannot construct a counterexample — the guard ensures link_id < IEEE80211_MLD_MAX_NUM_LINKS before the array write.
Finding #3 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 7663 |
| Taint snippet | control = le16_to_cpu(prof->control); |
| Tainted var | link_id |
| Subscript | [] line 7699 |
| Sink snippet | sdata_dereference(sdata->vif.link_conf[link_id], sdata); |
| Possibly guarded | yes (heuristic) |
Dismissed: In the second loop, link_id comes from for_each_set_bit(&removed_links, IEEE80211_MLD_MAX_NUM_LINKS), which only iterates bits 0..IEEE80211_MLD_MAX_NUM_LINKS-1. removed_links is only populated with BIT(link_id) values that already passed the bounds check at line 7666. sdata->vif.link_conf has IEEE80211_MLD_MAX_NUM_LINKS entries. No counterexample possible.
Finding #4 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 7663 |
| Taint snippet | control = le16_to_cpu(prof->control); |
| Tainted var | link_id |
| Subscript | [] line 7707 |
| Sink snippet | if (link_removal_timeout[link_id] < 1) |
| Possibly guarded | yes (heuristic) |
Dismissed: Same reasoning as finding #3. link_id from for_each_set_bit with IEEE80211_MLD_MAX_NUM_LINKS bound is guaranteed in-range. link_removal_timeout is stack-allocated with that same size. No counterexample possible.
Finding #5 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 7663 |
| Taint snippet | control = le16_to_cpu(prof->control); |
| Tainted var | link_id |
| Subscript | [] line 7711 |
| Sink snippet | (link_removal_timeout[link_id] - 1); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same reasoning as findings #3 and #4. This branch is only reached when link_removal_timeout[link_id] >= 1 (line 7707), so the subtraction is also safe from underflow. No counterexample possible.
ieee80211_rx_mgmt_assoc_resp() — net/mac80211/mlme.c FP confidence=high
The scanner misidentifies the flow. 'status_code' from le16_to_cpu() is passed to ieee80211_destroy_assoc_data() as an enum assoc_status argument, not as a loop bound. Inside the callee, the loop at line 5333 iterates over ARRAY_SIZE(data.bss) — a compile-time constant derived from the struct definition — not over status_code. status_code controls only which branch of an if/else is taken (ASSOC_SUCCESS vs other), never the loop iteration count. The loop bound is statically determined.
Finding #1 — Category F — cross-function via ieee80211_destroy_assoc_data() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le16_to_cpu() line 7192 |
| Taint snippet | status_code = le16_to_cpu(mgmt->u.assoc_resp.status_code); |
| Tainted var | status_code |
| Call site | line 7357 — passes status_code to ieee80211_destroy_assoc_data() |
| Call snippet | ieee80211_destroy_assoc_data(sdata, |
| Loop | for_loop line 5333 |
| Sink snippet | for (i = 0; i < ARRAY_SIZE(data.bss); i++) |
| Possibly guarded | yes (heuristic) |
Dismissed: The taint analysis confused the data-flow path. status_code is indeed server-supplied (le16_to_cpu of a received frame field). It is passed as the second argument to ieee80211_destroy_assoc_data(), which has type enum assoc_status. Inside that function, status_code is used only in conditional comparisons (status != ASSOC_SUCCESS, status != ASSOC_REJECTED) to decide control flow. The for loop at line 5333 iterates 'for (i = 0; i < ARRAY_SIZE(data.bss); i++)' — ARRAY_SIZE is a compile-time constant and is completely independent of the status_code value. No counterexample can be constructed because status_code never touches the loop bound. This is a classic false positive where the taint tracker incorrectly attributed the loop bound taint to a value that only influences branch selection, not iteration count.
ieee80211_rx_mgmt_beacon() — net/mac80211/mlme.c FP confidence=high
All findings are false positives. The core issue is that ieee802_11_parse_elems_full() validates all element pointers and lengths before returning, establishing postconditions that cover all subsequent field accesses. The taint source (le16_to_cpu() of frame_control at line 8082) is used only to initialize parse_params.type, which is a locally-controlled routing value. The actual server-supplied beacon IE data flows through ieee802_11_parse_elems_full() which validates element bounds. Specific findings: #1 is a type analysis error (u8[0] to u8 is not truncation); #2's ssid_len is parser-validated and guarded before use; #3 uses sizeof() which is compile-time; #4-8 all have explicit link_id >= IEEE80211_MLD_MAX_NUM_LINKS bounds checks before array use; #9's ttlm_num is parser-validated to reflect actual parsed element count.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 8340 |
| Taint snippet | erp_value = elems->erp_info[0]; |
| Tainted var | erp_value |
| Truncation | line 8340: 16 → 8-bit u8 |
| Sink snippet | erp_value = elems->erp_info[0]; |
| Possibly guarded | no |
Dismissed: False positive: elems->erp_info[0] is a u8 array element assigned to u8 erp_value. There is no 16-to-8 bit truncation here. The scanner incorrectly identified this as a widening/narrowing issue. The taint trace claiming le16_to_cpu() at line 8082 (frame_control) propagates to erp_info[0] is implausible — erp_info is a separate parsed element pointer populated by the element parser, not derived from frame_control.
Finding #2 — Category B — cross-function via ieee80211_mgd_ssid_mismatch() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 8082 |
| Taint snippet | struct ieee80211_elems_parse_params parse_params = { |
| Tainted var | elems |
| Call site | line 8207 — passes elems to ieee80211_mgd_ssid_mismatch() |
| Call snippet | ieee80211_mgd_ssid_mismatch(sdata, elems)) { |
| Sink (in callee) | memcmp() line 8022 (arg 2, role=size) |
| Sink snippet | if (!memcmp(elems->ssid, zero_ssid, elems->ssid_len)) |
| Possibly guarded | yes (heuristic) |
Dismissed: False positive. ieee802_11_parse_elems_full() validates all element pointers and lengths including ssid and ssid_len. Inside ieee80211_mgd_ssid_mismatch(), there are guards: ssid_len==0 returns false, ssid_len != cfg->ssid_len returns true (without memcmp), so the memcmp at line 8022 is only reached when ssid_len equals cfg->ssid_len which is a locally-controlled value (already validated SSID length). No counterexample possible: any ssid_len that could cause OOB would either be 0 (caught) or differ from cfg->ssid_len (caught) before reaching memcmp.
Finding #3 — Category B — cross-function via ieee80211_config_bw() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le16_to_cpu() line 8082 |
| Taint snippet | struct ieee80211_elems_parse_params parse_params = { |
| Tainted var | elems |
| Call site | line 8395 — passes elems to ieee80211_config_bw() |
| Call snippet | if (ieee80211_config_bw(link, elems, true, &changed, |
| Sink (in callee) | memcmp() line 1690 (arg 2, role=size) |
| Sink snippet | if (memcmp(&link->conf->tpe, &elems->tpe, sizeof(elems->tpe))) { |
| Possibly guarded | no |
Dismissed: False positive. The memcmp sink at line 1690 uses sizeof(elems->tpe) as the size argument, which is a compile-time constant sizeof — not a server-supplied value. The scanner incorrectly flagged a sizeof expression as a tainted size argument. No possible counterexample since sizeof is always safe.
Finding #4 — Category H — cross-function via ieee80211_ml_reconfiguration() — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 8082 |
| Taint snippet | struct ieee80211_elems_parse_params parse_params = { |
| Tainted var | elems |
| Call site | line 8418 — passes elems to ieee80211_ml_reconfiguration() |
| Call snippet | ieee80211_ml_reconfiguration(sdata, elems); |
| Truncation | line 7664: None → None-bit |
| Sink snippet | link_id = control & IEEE80211_MLE_STA_RECONF_CONTROL_LINK_ID; |
| Possibly guarded | no |
Dismissed: False positive. The narrowing at line 7664 extracts link_id via masking (& IEEE80211_MLE_STA_RECONF_CONTROL_LINK_ID), which limits the value to the LINK_ID bit field range. Immediately after, line 7666 checks link_id >= IEEE80211_MLD_MAX_NUM_LINKS and continues (skips) if out of range. The narrowing itself is intentional bit-field extraction, not a vulnerability.
Finding #5 — Category C — cross-function via ieee80211_ml_reconfiguration() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 8082 |
| Taint snippet | struct ieee80211_elems_parse_params parse_params = { |
| Tainted var | elems |
| Call site | line 8418 — passes elems to ieee80211_ml_reconfiguration() |
| Call snippet | ieee80211_ml_reconfiguration(sdata, elems); |
| Subscript (in callee) | [] line 7682 |
| Sink snippet | link_removal_timeout[link_id] = get_unaligned_le16(pos); |
| Possibly guarded | yes (heuristic) |
Dismissed: False positive. At line 7666: if (link_id >= IEEE80211_MLD_MAX_NUM_LINKS) continue; This guards the array access at line 7682 (link_removal_timeout[link_id]). The array has IEEE80211_MLD_MAX_NUM_LINKS entries. No counterexample: any link_id >= IEEE80211_MLD_MAX_NUM_LINKS is skipped by the continue statement before reaching the array indexing.
Finding #6 — Category C — cross-function via ieee80211_ml_reconfiguration() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 8082 |
| Taint snippet | struct ieee80211_elems_parse_params parse_params = { |
| Tainted var | elems |
| Call site | line 8418 — passes elems to ieee80211_ml_reconfiguration() |
| Call snippet | ieee80211_ml_reconfiguration(sdata, elems); |
| Subscript (in callee) | [] line 7699 |
| Sink snippet | sdata_dereference(sdata->vif.link_conf[link_id], sdata); |
| Possibly guarded | yes (heuristic) |
Dismissed: False positive. Same link_id >= IEEE80211_MLD_MAX_NUM_LINKS guard at line 7666 protects all subsequent uses of link_id as array index within this loop iteration, including sdata->vif.link_conf[link_id]. No counterexample possible given the explicit bounds check with continue.
Finding #7 — Category C — cross-function via ieee80211_ml_reconfiguration() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 8082 |
| Taint snippet | struct ieee80211_elems_parse_params parse_params = { |
| Tainted var | elems |
| Call site | line 8418 — passes elems to ieee80211_ml_reconfiguration() |
| Call snippet | ieee80211_ml_reconfiguration(sdata, elems); |
| Subscript (in callee) | [] line 7707 |
| Sink snippet | if (link_removal_timeout[link_id] < 1) |
| Possibly guarded | yes (heuristic) |
Dismissed: False positive. Same guard as findings #5 and #6. link_removal_timeout[link_id] at line 7707 is protected by the bounds check at line 7666 which skips iterations where link_id >= IEEE80211_MLD_MAX_NUM_LINKS.
Finding #8 — Category C — cross-function via ieee80211_ml_reconfiguration() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 8082 |
| Taint snippet | struct ieee80211_elems_parse_params parse_params = { |
| Tainted var | elems |
| Call site | line 8418 — passes elems to ieee80211_ml_reconfiguration() |
| Call snippet | ieee80211_ml_reconfiguration(sdata, elems); |
| Subscript (in callee) | [] line 7711 |
| Sink snippet | (link_removal_timeout[link_id] - 1); |
| Possibly guarded | yes (heuristic) |
Dismissed: False positive. Same protection as findings #5, #6, #7. The link_id >= IEEE80211_MLD_MAX_NUM_LINKS check with continue at line 7666 prevents any out-of-bounds link_id from reaching line 7711.
Finding #9 — Category F — cross-function via ieee80211_process_adv_ttlm() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | le16_to_cpu() line 8082 |
| Taint snippet | struct ieee80211_elems_parse_params parse_params = { |
| Tainted var | elems |
| Call site | line 8419 — passes elems to ieee80211_process_adv_ttlm() |
| Call snippet | ieee80211_process_adv_ttlm(sdata, elems, |
| Loop | for_loop line 7841 |
| Sink snippet | for (i = 0; i < elems->ttlm_num; i++) { |
| Possibly guarded | no |
Dismissed: False positive. elems->ttlm_num and elems->ttlm[] are populated by ieee802_11_parse_elems_full() which validates that ttlm_num TT-LM elements exist within the input buffer. The loop at line 7841 iterates exactly over the parsed elements; ttlm_num reflects the actual count of validated elements stored in the ttlm[] array. The loop bound is inherently safe because the parser bounds-checks during population. Each loop body calls ieee80211_parse_adv_t2l() which performs additional validation.
ieee80211_verify_peer_he_mcs_support() — net/mac80211/mlme.c FP confidence=high
All three findings involve assigning a bitmask-extracted 2-bit field (masked with & 3, yielding values 0-3) from a 16-bit server-supplied value into a u8 variable. The & 3 masking operation strictly limits the assigned value to the range [0,3] before the assignment, making truncation impossible. The scanner's own note explicitly states to flag as false positive when & mask limits the value to destination width — which is exactly the case here. No valid counterexample can be constructed where a 16-bit value shifted and ANDed with 3 would produce a value exceeding u8 range.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 720 |
| Taint snippet | u8 ap_op_val = (ap_min_req_set >> (2 * (nss - 1))) & 3; |
| Tainted var | ap_op_val |
| Truncation | line 720: 16 → 8-bit u8 |
| Sink snippet | u8 ap_op_val = (ap_min_req_set >> (2 * (nss - 1))) & 3; |
| Possibly guarded | no |
Dismissed: The expression `(ap_min_req_set >> (2 * (nss - 1))) & 3` uses `& 3` to mask the result to exactly 2 bits (values 0-3) before assignment to u8. No truncation is possible — cannot construct a counterexample. False positive per the scanner's own masking exception rule.
Finding #2 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 727 |
| Taint snippet | ap_rx_val = (mcs_80_map_rx >> (2 * (nss - 1))) & 3; |
| Tainted var | ap_rx_val |
| Truncation | line 727: 16 → 8-bit u8 |
| Sink snippet | ap_rx_val = (mcs_80_map_rx >> (2 * (nss - 1))) & 3; |
| Possibly guarded | yes (heuristic) |
Dismissed: The expression `(mcs_80_map_rx >> (2 * (nss - 1))) & 3` masks to 2 bits before u8 assignment. Result is always in [0,3]. No truncation possible; no counterexample can be constructed.
Finding #3 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 728 |
| Taint snippet | ap_tx_val = (mcs_80_map_tx >> (2 * (nss - 1))) & 3; |
| Tainted var | ap_tx_val |
| Truncation | line 728: 16 → 8-bit u8 |
| Sink snippet | ap_tx_val = (mcs_80_map_tx >> (2 * (nss - 1))) & 3; |
| Possibly guarded | yes (heuristic) |
Dismissed: The expression `(mcs_80_map_tx >> (2 * (nss - 1))) & 3` masks to 2 bits before u8 assignment. Result is always in [0,3]. No truncation possible; no counterexample can be constructed.
ieee80211_verify_sta_he_mcs_support() — net/mac80211/mlme.c FP confidence=high
All three findings involve extracting 2-bit fields from 16-bit MCS maps using right-shift and '& 3' masking. The result is always in [0,3], which fits trivially in u8. The scanner note itself says to flag as false positive when masking limits the value to the destination width — that condition is satisfied here. No truncation loss is possible regardless of the server-supplied 16-bit input values.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 787 |
| Taint snippet | u8 sta_rx_val = (sta_mcs_map_rx >> (2 * (nss - 1))) & 3; |
| Tainted var | sta_rx_val |
| Truncation | line 787: 16 → 8-bit u8 |
| Sink snippet | u8 sta_rx_val = (sta_mcs_map_rx >> (2 * (nss - 1))) & 3; |
| Possibly guarded | no |
Dismissed: The expression '(sta_mcs_map_rx >> (2 * (nss - 1))) & 3' uses '& 3' to mask the result to exactly 2 bits (values 0-3). No 16-bit input can produce a value outside [0,3] after this masking, so assigning to u8 is safe. No counterexample exists.
Finding #2 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 788 |
| Taint snippet | u8 sta_tx_val = (sta_mcs_map_tx >> (2 * (nss - 1))) & 3; |
| Tainted var | sta_tx_val |
| Truncation | line 788: 16 → 8-bit u8 |
| Sink snippet | u8 sta_tx_val = (sta_mcs_map_tx >> (2 * (nss - 1))) & 3; |
| Possibly guarded | no |
Dismissed: The expression '(sta_mcs_map_tx >> (2 * (nss - 1))) & 3' uses '& 3' to mask the result to exactly 2 bits (values 0-3). No 16-bit input can produce a value outside [0,3] after this masking, so assigning to u8 is safe. No counterexample exists.
Finding #3 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 789 |
| Taint snippet | u8 ap_val = (ap_min_req_set >> (2 * (nss - 1))) & 3; |
| Tainted var | ap_val |
| Truncation | line 789: 16 → 8-bit u8 |
| Sink snippet | u8 ap_val = (ap_min_req_set >> (2 * (nss - 1))) & 3; |
| Possibly guarded | no |
Dismissed: The expression '(ap_min_req_set >> (2 * (nss - 1))) & 3' uses '& 3' to mask the result to exactly 2 bits (values 0-3). ap_min_req_set comes from le16_to_cpu(he_op->he_mcs_nss_set) (AP-supplied), but after masking, the value is always in [0,3], safely fitting in u8. No counterexample exists.
ieee80211_verify_sta_vht_mcs_support() — net/mac80211/mlme.c FP confidence=high
All three flagged truncations are safe because the RHS expression applies '& 3' masking before assignment to u8, limiting the value to {0,1,2,3} regardless of the 16-bit source width. No counterexample can be constructed where a value passes the mask yet exceeds u8 range. Findings #2 and #3 also involve locally-copied station capabilities rather than raw server-supplied data.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 634 |
| Taint snippet | u8 ap_op_val = (ap_min_req_set >> (2 * (nss - 1))) & 3; |
| Tainted var | ap_op_val |
| Truncation | line 634: 16 → 8-bit u8 |
| Sink snippet | u8 ap_op_val = (ap_min_req_set >> (2 * (nss - 1))) & 3; |
| Possibly guarded | no |
Dismissed: The expression '(ap_min_req_set >> (2 * (nss - 1))) & 3' applies '& 3' before assignment to u8. The maximum possible value is 3, which fits trivially in u8. No counterexample exists: any 16-bit input after right-shift and '& 3' yields a value in [0,3], well within u8 range. The truncation is safe.
Finding #2 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 641 |
| Taint snippet | sta_rx_val = (sta_rx_mcs_map >> (2 * (nss - 1))) & 3; |
| Tainted var | sta_rx_val |
| Truncation | line 641: 16 → 8-bit u8 |
| Sink snippet | sta_rx_val = (sta_rx_mcs_map >> (2 * (nss - 1))) & 3; |
| Possibly guarded | no |
Dismissed: sta_rx_mcs_map comes from a local copy of the station's own VHT capabilities (sband->vht_cap, then overridden by ieee80211_apply_vhtcap_overrides), not from the server's VHT operation. Additionally, '& 3' masks the result to [0,3] before u8 assignment — no truncation issue is possible.
Finding #3 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 642 |
| Taint snippet | sta_tx_val = (sta_tx_mcs_map >> (2 * (nss - 1))) & 3; |
| Tainted var | sta_tx_val |
| Truncation | line 642: 16 → 8-bit u8 |
| Sink snippet | sta_tx_val = (sta_tx_mcs_map >> (2 * (nss - 1))) & 3; |
| Possibly guarded | no |
Dismissed: sta_tx_mcs_map comes from the same locally-copied station VHT capability structure, not server-supplied. The '& 3' mask limits the value to [0,3] before u8 assignment. No truncation issue possible; false positive.
ieee80211_rx_h_ctrl() — net/mac80211/rx.c FP confidence=high
The function reads a BAR control field from a received 802.11 frame via skb_copy_bits(). The tid value is extracted as (u16 >> 12), which constrains it to [0, 15]. The tid_rx array has IEEE80211_NUM_TIDS=16 elements (indices 0-15), so no out-of-bounds access is possible. No counterexample exists. The scanner flagged a le16_to_cpu result without recognising that the subsequent right-shift by 12 eliminates all risk.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 3395 |
| Taint snippet | tid = le16_to_cpu(bar_data.control) >> 12; |
| Tainted var | tid |
| Subscript | [] line 3405 |
| Sink snippet | tid_agg_rx = rcu_dereference(rx->sta->ampdu_mlme.tid_rx[tid]); |
| Possibly guarded | no |
Dismissed: tid = le16_to_cpu(bar_data.control) >> 12 yields a value in [0, 15]. tid_rx[] has IEEE80211_NUM_TIDS=16 entries, so valid indices are [0, 15]. No counterexample can be constructed — the 4-bit result cannot exceed 15, and the array bound is 16. This is a false positive from the static analyser not tracking the effect of the >> 12 shift on a u16.
ieee80211_rx_h_defragment() — net/mac80211/rx.c FP confidence=high
The scanner incorrectly traces taint from le16_to_cpu(hdr->seq_ctrl) through 'entry' to the memcpy destinations inside pskb_expand_head(). In reality: (1) 'entry' is a pointer to a fixed-size struct within a bounded cache array, not an arbitrary server-controlled pointer; (2) pskb_expand_head() is called with entry->extra_len as the 'ntail' size parameter, not as a pointer; (3) the memcpy destinations inside pskb_expand_head() are 'data' (freshly kmalloc'd) and skb internals — neither is derived from the tainted value; (4) entry->extra_len accumulates actual skb->len values of received fragments, bounded by real MTU constraints, not a raw unchecked header field.
Finding #1 — Category A — cross-function via pskb_expand_head() — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | le16_to_cpu() line 2390 |
| Taint snippet | sc = le16_to_cpu(hdr->seq_ctrl); |
| Tainted var | entry |
| Call site | line 2513 — passes entry to pskb_expand_head() |
| Call snippet | if (unlikely(pskb_expand_head(rx->skb, 0, entry->extra_len, |
| Sink (in callee) | memcpy() line 2320 (arg 0, role=pointer) |
| Sink snippet | memcpy(data + nhead, skb->head, skb_tail_pointer(skb) - skb->head); |
| Possibly guarded | yes (heuristic) |
Dismissed: The scanner misattributes the memcpy destination pointer (arg 0) as tainted. The memcpy at pskb_expand_head:2320 copies from skb->head to 'data' (a freshly allocated buffer). Neither 'data' nor the copy length (skb_tail_pointer(skb)-skb->head) is derived from entry->extra_len or sc. The 'entry' pointer itself is a valid struct pointer from a bounded cache array, and entry->extra_len is accumulated from actual skb->len values. No counterexample can be constructed where sc values cause an OOB memcpy at line 2320 — the memcpy size is derived from skb internal pointers, not from the tainted field.
Finding #2 — Category A — cross-function via pskb_expand_head() — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | le16_to_cpu() line 2390 |
| Taint snippet | sc = le16_to_cpu(hdr->seq_ctrl); |
| Tainted var | entry |
| Call site | line 2513 — passes entry to pskb_expand_head() |
| Call snippet | if (unlikely(pskb_expand_head(rx->skb, 0, entry->extra_len, |
| Sink (in callee) | memcpy() line 2322 (arg 0, role=pointer) |
| Sink snippet | memcpy((struct skb_shared_info *)(data + size), |
| Possibly guarded | yes (heuristic) |
Dismissed: Same reasoning as finding #1. The memcpy at pskb_expand_head:2322 copies skb_shinfo data into (data+size) where 'size' = osize + 0 + ntail, with ntail=entry->extra_len. The destination pointer is 'data+size' (within the kmalloc'd region) and the source/size come from skb_shinfo(skb)->nr_frags — not from the tainted 'sc' value. The scanner incorrectly identifies the memcpy destination as tainted. No valid counterexample exists where seq_ctrl values cause this specific memcpy to be unsafe.
ieee80211_sta_nss_capability() — net/mac80211/sta_info.c FP confidence=high
All three findings involve extracting 2-bit fields via '& 3' from a 16-bit le16_to_cpu() result and assigning to u8. The masking to 2 bits (values 0-3) ensures no information loss when stored in u8. The scanner's own note says to flag as false positive when the RHS uses masking that limits the value to the destination width, which is exactly the case here. Additionally, the extracted values are only used in equality comparisons (against MCS_NOT_SUPPORTED sentinel values) or to set a small loop index (i+1, max 8), with no array indexing, allocation sizing, or other dangerous sink.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 3485 |
| Taint snippet | u8 mcs_160 = (mcs_160_map >> (2 * i)) & 3; |
| Tainted var | mcs_160 |
| Truncation | line 3485: 16 → 8-bit u8 |
| Sink snippet | u8 mcs_160 = (mcs_160_map >> (2 * i)) & 3; |
| Possibly guarded | no |
Dismissed: The expression '(mcs_160_map >> (2 * i)) & 3' masks to exactly 2 bits before assignment to u8. Values 0-3 fit trivially in u8. No counterexample can be constructed: the maximum post-mask value is 3, well within u8 range. The value is only compared to IEEE80211_HE_MCS_NOT_SUPPORTED (a 2-bit constant). False positive per the scanner's own masking criterion.
Finding #2 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 3493 |
| Taint snippet | u8 mcs_80 = (mcs_80_map >> (2 * i)) & 3; |
| Tainted var | mcs_80 |
| Truncation | line 3493: 16 → 8-bit u8 |
| Sink snippet | u8 mcs_80 = (mcs_80_map >> (2 * i)) & 3; |
| Possibly guarded | no |
Dismissed: Identical pattern to finding #1. '(mcs_80_map >> (2 * i)) & 3' masks to 2 bits before u8 assignment. Maximum value is 3. No dangerous sink; only used in a comparison. False positive.
Finding #3 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 3529 |
| Taint snippet | u8 mcs = (rx_mcs_map >> (2 * i)) & 3; |
| Tainted var | mcs |
| Truncation | line 3529: 16 → 8-bit u8 |
| Sink snippet | u8 mcs = (rx_mcs_map >> (2 * i)) & 3; |
| Possibly guarded | no |
Dismissed: Same pattern: '(rx_mcs_map >> (2 * i)) & 3' masks to 2 bits before u8 assignment. Maximum value is 3. Used only in a comparison against IEEE80211_VHT_MCS_NOT_SUPPORTED. False positive.
__ieee80211_tx_status() — net/mac80211/status.c FP confidence=high
All three findings are false positives. For #1 and #2: tid at the array sinks (lines 1077/1079) is guarded by ieee80211_is_data_present(fc), while the BAR-derived tid (from le16_to_cpu) is set under ieee80211_is_back_req(fc) — a control frame, not a data frame — making these paths mutually exclusive. When the sinks are reachable, tid comes from qc[0] & 0xf which is bounded to 0-15. For #3: the BAR TID field is 4 bits (IEEE80211_BAR_CTRL_TID_INFO_MASK/SHIFT gives 0-15), and tid_tx[] has IEEE80211_NUM_TIDS=16 entries, so no OOB is possible.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 1060 |
| Taint snippet | control = le16_to_cpu(bar->control); |
| Tainted var | tid |
| Subscript | [] line 1077 |
| Sink snippet | sta->deflink.status_stats.msdu_failed[tid]++; |
| Possibly guarded | no |
Dismissed: The sink at line 1077 is under ieee80211_is_data_present(fc). The BAR-derived tid is set only when ieee80211_is_back_req(fc) is true, which is a control frame — ieee80211_is_data_present() will be false for that frame type. So the tainted tid never reaches the sink. When the sink IS reachable, tid = qc[0] & 0xf (0-15), which is in bounds for msdu_failed[] sized IEEE80211_NUM_TIDS=16. No counterexample possible.
Finding #2 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 1060 |
| Taint snippet | control = le16_to_cpu(bar->control); |
| Tainted var | tid |
| Subscript | [] line 1079 |
| Sink snippet | sta->deflink.status_stats.msdu_retries[tid] += |
| Possibly guarded | no |
Dismissed: Same reasoning as finding #1. The sink at line 1079 is under ieee80211_is_data_present(fc), which is mutually exclusive with the BAR path. When reachable, tid = qc[0] & 0xf (0-15), within bounds.
Finding #3 — Category C — cross-function via ieee80211_set_bar_pending() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le16_to_cpu() line 1060 |
| Taint snippet | control = le16_to_cpu(bar->control); |
| Tainted var | tid |
| Call site | line 1068 — passes tid to ieee80211_set_bar_pending() |
| Call snippet | ieee80211_set_bar_pending(sta, tid, ssn); |
| Subscript (in callee) | [] line 201 |
| Sink snippet | tid_tx = rcu_dereference(sta->ampdu_mlme.tid_tx[tid]); |
| Possibly guarded | no |
Dismissed: IEEE80211_BAR_CTRL_TID_INFO_MASK is 0xF000 and IEEE80211_BAR_CTRL_TID_INFO_SHIFT is 12, so (control & mask) >> shift yields 0-15. sta->ampdu_mlme.tid_tx[] is sized IEEE80211_NUM_TIDS=16 (indices 0-15). No out-of-bounds access is possible. No counterexample can be constructed.
tkip_mixing_phase1() — net/mac80211/tkip.c FP confidence=high
All five findings are false positives. The taint source 'tk' is TKIP temporal key material stored in kernel-local key structures (key->conf.key[]), not a server/network-supplied value. The 'ta' parameter is a 6-byte MAC address and 'tsc_IV32' is a u32 counter. More critically, tkipS() masks its u16 argument with '& 0xff' and '>> 8' before indexing tkip_sbox[], guaranteeing both subscripts are in [0,255] — which is exactly the valid range for the 256-entry sbox — regardless of the input value. No counterexample can be constructed that produces an out-of-bounds access, because the masking is complete for a u16 input.
Finding #1 — Category C — cross-function via tkipS() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | get_unaligned_le16() line 96 |
| Taint snippet | p1k[0] += tkipS(p1k[4] ^ get_unaligned_le16(tk + 0 + j)); |
| Tainted var | p1k[4] ^ get_unaligned_le16(tk + 0 + j) |
| Call site | line 96 — passes p1k[4] ^ get_unaligned_le16(tk + 0 + j) to tkipS() |
| Call snippet | p1k[0] += tkipS(p1k[4] ^ get_unaligned_le16(tk + 0 + j)); |
| Subscript (in callee) | [] line 64 |
| Sink snippet | return tkip_sbox[val & 0xff] ^ swab16(tkip_sbox[val >> 8]); |
| Possibly guarded | no |
Dismissed: tk is local key material, not a network packet field. tkipS() uses 'val & 0xff' and 'val >> 8' — both always in [0,255] for any u16 input. No counterexample possible; array access is unconditionally safe.
Finding #2 — Category C — cross-function via tkipS() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | get_unaligned_le16() line 97 |
| Taint snippet | p1k[1] += tkipS(p1k[0] ^ get_unaligned_le16(tk + 4 + j)); |
| Tainted var | p1k[0] ^ get_unaligned_le16(tk + 4 + j) |
| Call site | line 97 — passes p1k[0] ^ get_unaligned_le16(tk + 4 + j) to tkipS() |
| Call snippet | p1k[1] += tkipS(p1k[0] ^ get_unaligned_le16(tk + 4 + j)); |
| Subscript (in callee) | [] line 64 |
| Sink snippet | return tkip_sbox[val & 0xff] ^ swab16(tkip_sbox[val >> 8]); |
| Possibly guarded | no |
Dismissed: Same reasoning as finding #1. XOR of two u16 values is u16; masking in tkipS() constrains both indices to [0,255]. False positive.
Finding #3 — Category C — cross-function via tkipS() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | get_unaligned_le16() line 98 |
| Taint snippet | p1k[2] += tkipS(p1k[1] ^ get_unaligned_le16(tk + 8 + j)); |
| Tainted var | p1k[1] ^ get_unaligned_le16(tk + 8 + j) |
| Call site | line 98 — passes p1k[1] ^ get_unaligned_le16(tk + 8 + j) to tkipS() |
| Call snippet | p1k[2] += tkipS(p1k[1] ^ get_unaligned_le16(tk + 8 + j)); |
| Subscript (in callee) | [] line 64 |
| Sink snippet | return tkip_sbox[val & 0xff] ^ swab16(tkip_sbox[val >> 8]); |
| Possibly guarded | no |
Dismissed: Same reasoning as finding #1. False positive.
Finding #4 — Category C — cross-function via tkipS() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | get_unaligned_le16() line 99 |
| Taint snippet | p1k[3] += tkipS(p1k[2] ^ get_unaligned_le16(tk + 12 + j)); |
| Tainted var | p1k[2] ^ get_unaligned_le16(tk + 12 + j) |
| Call site | line 99 — passes p1k[2] ^ get_unaligned_le16(tk + 12 + j) to tkipS() |
| Call snippet | p1k[3] += tkipS(p1k[2] ^ get_unaligned_le16(tk + 12 + j)); |
| Subscript (in callee) | [] line 64 |
| Sink snippet | return tkip_sbox[val & 0xff] ^ swab16(tkip_sbox[val >> 8]); |
| Possibly guarded | no |
Dismissed: Same reasoning as finding #1. False positive.
Finding #5 — Category C — cross-function via tkipS() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | get_unaligned_le16() line 100 |
| Taint snippet | p1k[4] += tkipS(p1k[3] ^ get_unaligned_le16(tk + 0 + j)) + i; |
| Tainted var | p1k[3] ^ get_unaligned_le16(tk + 0 + j) |
| Call site | line 100 — passes p1k[3] ^ get_unaligned_le16(tk + 0 + j) to tkipS() |
| Call snippet | p1k[4] += tkipS(p1k[3] ^ get_unaligned_le16(tk + 0 + j)) + i; |
| Subscript (in callee) | [] line 64 |
| Sink snippet | return tkip_sbox[val & 0xff] ^ swab16(tkip_sbox[val >> 8]); |
| Possibly guarded | no |
Dismissed: Same reasoning as finding #1. False positive.
tkip_mixing_phase2() — net/mac80211/tkip.c FP confidence=high
All findings are false positives. The tkipS() function receives a u16 argument and uses it as two array indices: (val & 0xff) and (val >> 8). Both expressions are mathematically constrained to [0, 255] regardless of the input value — 'val & 0xff' masks to 8 bits, and 'val >> 8' on a u16 also yields at most 8 bits. The tkip_sbox[] array has exactly 256 entries, so no OOB access is possible. The static scanner flagged 'tainted u16 used as array subscript' without recognizing that the masking and shifting make the index safe by construction. No counterexample exists because no u16 value can produce an index outside [0,255] through these operations.
Finding #1 — Category C — cross-function via tkipS() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | get_unaligned_le16() line 120 |
| Taint snippet | ppk[0] += tkipS(ppk[5] ^ get_unaligned_le16(tk + 0)); |
| Tainted var | ppk[5] ^ get_unaligned_le16(tk + 0) |
| Call site | line 120 — passes ppk[5] ^ get_unaligned_le16(tk + 0) to tkipS() |
| Call snippet | ppk[0] += tkipS(ppk[5] ^ get_unaligned_le16(tk + 0)); |
| Subscript (in callee) | [] line 64 |
| Sink snippet | return tkip_sbox[val & 0xff] ^ swab16(tkip_sbox[val >> 8]); |
| Possibly guarded | no |
Dismissed: tkipS() masks val to 8-bit indices: (val & 0xff) ∈ [0,255] and (val >> 8) ∈ [0,255]. tkip_sbox has 256 entries. No OOB possible regardless of val's value. Counterexample: no u16 value produces index > 255 through these operations.
Finding #2 — Category C — cross-function via tkipS() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | get_unaligned_le16() line 121 |
| Taint snippet | ppk[1] += tkipS(ppk[0] ^ get_unaligned_le16(tk + 2)); |
| Tainted var | ppk[0] ^ get_unaligned_le16(tk + 2) |
| Call site | line 121 — passes ppk[0] ^ get_unaligned_le16(tk + 2) to tkipS() |
| Call snippet | ppk[1] += tkipS(ppk[0] ^ get_unaligned_le16(tk + 2)); |
| Subscript (in callee) | [] line 64 |
| Sink snippet | return tkip_sbox[val & 0xff] ^ swab16(tkip_sbox[val >> 8]); |
| Possibly guarded | no |
Dismissed: Same reasoning as finding #1. The u16 XOR result passed to tkipS() is always bounded to [0,65535], and tkipS() internally masks to [0,255] for both accesses. No counterexample possible.
Finding #3 — Category C — cross-function via tkipS() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | get_unaligned_le16() line 122 |
| Taint snippet | ppk[2] += tkipS(ppk[1] ^ get_unaligned_le16(tk + 4)); |
| Tainted var | ppk[1] ^ get_unaligned_le16(tk + 4) |
| Call site | line 122 — passes ppk[1] ^ get_unaligned_le16(tk + 4) to tkipS() |
| Call snippet | ppk[2] += tkipS(ppk[1] ^ get_unaligned_le16(tk + 4)); |
| Subscript (in callee) | [] line 64 |
| Sink snippet | return tkip_sbox[val & 0xff] ^ swab16(tkip_sbox[val >> 8]); |
| Possibly guarded | no |
Dismissed: Same reasoning as finding #1. tkipS() masks u16 to safe [0,255] indices unconditionally.
Finding #4 — Category C — cross-function via tkipS() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | get_unaligned_le16() line 123 |
| Taint snippet | ppk[3] += tkipS(ppk[2] ^ get_unaligned_le16(tk + 6)); |
| Tainted var | ppk[2] ^ get_unaligned_le16(tk + 6) |
| Call site | line 123 — passes ppk[2] ^ get_unaligned_le16(tk + 6) to tkipS() |
| Call snippet | ppk[3] += tkipS(ppk[2] ^ get_unaligned_le16(tk + 6)); |
| Subscript (in callee) | [] line 64 |
| Sink snippet | return tkip_sbox[val & 0xff] ^ swab16(tkip_sbox[val >> 8]); |
| Possibly guarded | no |
Dismissed: Same reasoning as finding #1. tkipS() masks u16 to safe [0,255] indices unconditionally.
Finding #5 — Category C — cross-function via tkipS() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | get_unaligned_le16() line 124 |
| Taint snippet | ppk[4] += tkipS(ppk[3] ^ get_unaligned_le16(tk + 8)); |
| Tainted var | ppk[3] ^ get_unaligned_le16(tk + 8) |
| Call site | line 124 — passes ppk[3] ^ get_unaligned_le16(tk + 8) to tkipS() |
| Call snippet | ppk[4] += tkipS(ppk[3] ^ get_unaligned_le16(tk + 8)); |
| Subscript (in callee) | [] line 64 |
| Sink snippet | return tkip_sbox[val & 0xff] ^ swab16(tkip_sbox[val >> 8]); |
| Possibly guarded | no |
Dismissed: Same reasoning as finding #1. tkipS() masks u16 to safe [0,255] indices unconditionally.
Finding #6 — Category C — cross-function via tkipS() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | get_unaligned_le16() line 125 |
| Taint snippet | ppk[5] += tkipS(ppk[4] ^ get_unaligned_le16(tk + 10)); |
| Tainted var | ppk[4] ^ get_unaligned_le16(tk + 10) |
| Call site | line 125 — passes ppk[4] ^ get_unaligned_le16(tk + 10) to tkipS() |
| Call snippet | ppk[5] += tkipS(ppk[4] ^ get_unaligned_le16(tk + 10)); |
| Subscript (in callee) | [] line 64 |
| Sink snippet | return tkip_sbox[val & 0xff] ^ swab16(tkip_sbox[val >> 8]); |
| Possibly guarded | no |
Dismissed: Same reasoning as finding #1. tkipS() masks u16 to safe [0,255] indices unconditionally.
ieee80211_apply_vhtcap_overrides() — net/mac80211/vht.c FP confidence=high
All four findings are false positives. The scanner correctly identifies 16-bit to 8-bit truncation, but misses that the bitwise AND with IEEE80211_VHT_MCS_NOT_SUPPORTED (value=3, i.e., 0x3) limits any result to at most 2 bits (values 0-3) before assignment to u8. No truncation of meaningful bits can occur. Additionally, the mask/capa fields from sdata->u.mgd are user-space configured overrides (via nl80211), not server-supplied network data, further reducing concern.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 91 |
| Taint snippet | m = (rxmcs_mask >> 2*i) & IEEE80211_VHT_MCS_NOT_SUPPORTED; |
| Tainted var | m |
| Truncation | line 91: 16 → 8-bit u8 |
| Sink snippet | m = (rxmcs_mask >> 2*i) & IEEE80211_VHT_MCS_NOT_SUPPORTED; |
| Possibly guarded | no |
Dismissed: rxmcs_mask comes from sdata->u.mgd.vht_capa_mask (user nl80211 config, not server). The expression (rxmcs_mask >> 2*i) & IEEE80211_VHT_MCS_NOT_SUPPORTED masks to 2 bits (max value 3), which trivially fits in u8. No counterexample exists that could cause truncation of meaningful bits.
Finding #2 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 93 |
| Taint snippet | c = (rxmcs_cap >> 2*i) & IEEE80211_VHT_MCS_NOT_SUPPORTED; |
| Tainted var | c |
| Truncation | line 93: 16 → 8-bit u8 |
| Sink snippet | c = (rxmcs_cap >> 2*i) & IEEE80211_VHT_MCS_NOT_SUPPORTED; |
| Possibly guarded | no |
Dismissed: rxmcs_cap comes from vht_cap->vht_mcs.rx_mcs_map (AP/server-supplied). However, (rxmcs_cap >> 2*i) & IEEE80211_VHT_MCS_NOT_SUPPORTED masks to exactly 2 bits (values 0-3). No 16-bit value, regardless of what the server sends, can produce a result exceeding 3 after this mask. The truncation to u8 is entirely safe. No counterexample is constructible.
Finding #3 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 101 |
| Taint snippet | m = (txmcs_mask >> 2*i) & IEEE80211_VHT_MCS_NOT_SUPPORTED; |
| Tainted var | m |
| Truncation | line 101: 16 → 8-bit u8 |
| Sink snippet | m = (txmcs_mask >> 2*i) & IEEE80211_VHT_MCS_NOT_SUPPORTED; |
| Possibly guarded | no |
Dismissed: txmcs_mask comes from sdata->u.mgd.vht_capa_mask (user nl80211 config, not server). The expression (txmcs_mask >> 2*i) & IEEE80211_VHT_MCS_NOT_SUPPORTED masks to 2 bits (max value 3), fitting trivially in u8. No counterexample exists.
Finding #4 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 103 |
| Taint snippet | c = (txmcs_cap >> 2*i) & IEEE80211_VHT_MCS_NOT_SUPPORTED; |
| Tainted var | c |
| Truncation | line 103: 16 → 8-bit u8 |
| Sink snippet | c = (txmcs_cap >> 2*i) & IEEE80211_VHT_MCS_NOT_SUPPORTED; |
| Possibly guarded | no |
Dismissed: txmcs_cap comes from vht_cap->vht_mcs.tx_mcs_map (AP/server-supplied). Same analysis as finding #2: the & IEEE80211_VHT_MCS_NOT_SUPPORTED mask (value=3) limits any 16-bit input to a 2-bit result before u8 assignment. No counterexample is constructible.
mctp_ioctl_tag_copy_from_user() — net/mctp/af_mctp.c FP confidence=high
The `size` variable in mctp_ioctl_tag_copy_from_user() is NOT user-supplied — it is derived entirely from compile-time sizeof() expressions. The taint analysis has misidentified copy_from_user() as a taint source for the `size` argument, but `size` is set to either sizeof(*ctl) (i.e., sizeof(struct mctp_ioc_tag_ctl2)) or sizeof(ctl_compat) (i.e., sizeof(struct mctp_ioc_tag_ctl)), both of which are kernel-internal constants determined at compile time. The `arg` (user pointer) is only used as the source address for the copy, not as the size. The destination buffers (`ctl` passed by pointer from callers, or the local `ctl_compat`) are stack-allocated with matching sizes. There is no user-controlled size in this function whatsoever.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_from_user() line 451 |
| Taint snippet | rc = copy_from_user(ptr, (void __user *)arg, size); |
| Tainted var | size |
| Unvalidated size | copy_from_user() arg 2 line 451 — size size |
| Sink snippet | rc = copy_from_user(ptr, (void __user *)arg, size); |
| Possibly guarded | no |
Dismissed: The `size` variable is set exclusively from sizeof() of kernel-defined structs (either sizeof(struct mctp_ioc_tag_ctl2) or sizeof(struct mctp_ioc_tag_ctl)), making it a compile-time constant — not user-supplied data. The copy_from_user() call uses `arg` only as the user-space source address, not as the size. The destination buffers are stack-allocated with exactly the matching size. No counterexample can be constructed where a user-controlled value reaches the size argument. This is a false positive from the taint analysis incorrectly propagating taint from copy_from_user() itself back onto the `size` parameter.
mctp_ioctl_tag_copy_to_user() — net/mctp/af_mctp.c FP confidence=high
The `size` variable in `mctp_ioctl_tag_copy_to_user()` is not user-controlled at all. It is set to either `sizeof(*ctl)` (a compile-time constant for `struct mctp_ioc_tag_ctl2`) or `sizeof(ctl_compat)` (a compile-time constant for `struct mctp_ioc_tag_ctl`), depending on the boolean `tagv2` parameter. Both values are fixed kernel-internal sizes determined at compile time. The `ctl` struct passed in is a locally-allocated stack variable in `mctp_ioctl_alloctag()`, filled by `mctp_ioctl_tag_copy_from_user()` and then modified by the kernel (e.g., `ctl.tag = tag | MCTP_TAG_OWNER | MCTP_TAG_PREALLOC`). No user-supplied size is ever used; the copy_to_user call copies a fixed-size kernel struct to userspace. The scanner's concern about an unvalidated user-controlled size is entirely unfounded here.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 495 |
| Taint snippet | rc = copy_to_user((void __user *)arg, ptr, size); |
| Tainted var | size |
| Unvalidated size | copy_to_user() arg 2 line 495 — size size |
| Sink snippet | rc = copy_to_user((void __user *)arg, ptr, size); |
| Possibly guarded | no |
Dismissed: The `size` variable is set to `sizeof(*ctl)` or `sizeof(ctl_compat)`, both compile-time constants. No user-controlled size is ever involved. The scanner appears to have confused the user-space destination address `arg` (which is user-supplied) with the size argument. The size is purely kernel-internal and fixed. No counterexample can be constructed because there is no dynamic size computation at all — the value is always one of two compile-time constants.
mptcp_get_subflow_data() — net/mptcp/sockopt.c FP confidence=high
The function has solid validation discipline. The copylen variable is capped by min_t(unsigned int, len, sizeof(*sfd)), where sizeof(*sfd) is a compile-time constant equal to the destination buffer size. The guard at line 1094 ensures len >= MIN_INFO_OPTLEN_SIZE, and BUILD_BUG_ON enforces sizeof(*sfd) == MIN_INFO_OPTLEN_SIZE. Thus copylen is always in [MIN_INFO_OPTLEN_SIZE, sizeof(*sfd)], exactly fitting the destination stack buffer. Additional validation of sfd->size_subflow_data and sfd->size_user fields follows the copy.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_from_user() line 1100 |
| Taint snippet | if (copy_from_user(sfd, optval, copylen)) |
| Tainted var | copylen |
| Unvalidated size | copy_from_user() arg 2 line 1100 — size copylen |
| Sink snippet | if (copy_from_user(sfd, optval, copylen)) |
| Possibly guarded | no |
Dismissed: copylen = min_t(unsigned int, len, sizeof(*sfd)) caps the copy size at sizeof(*sfd), which is exactly the size of the destination stack buffer sfd. The prior check len >= MIN_INFO_OPTLEN_SIZE combined with BUILD_BUG_ON(sizeof(*sfd) != MIN_INFO_OPTLEN_SIZE) ensures copylen is always within [MIN_INFO_OPTLEN_SIZE, sizeof(*sfd)]. No counterexample can be constructed where copylen exceeds the destination buffer size — the bounds check is tight and sufficient.
mptcp_put_full_info() — net/mptcp/sockopt.c FP confidence=high
The 'copylen' variable is not user-controlled in a dangerous sense. In the caller, it is computed as min_t(unsigned int, len, sizeof(struct mptcp_info)), which clamps it to a kernel compile-time constant. After adding MIN_FULL_INFO_OPTLEN_SIZE (another constant), the total cannot exceed sizeof(struct mptcp_full_info), which is the size of the local kernel struct 'mfi' being copied. There is no path where a user can supply an arbitrary size that causes OOB access of the source buffer. The taint originates from user-supplied optlen but is immediately clamped by a min() against a kernel constant before being used as the copy size.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 1303 |
| Taint snippet | if (copy_to_user(optval, mfi, copylen)) |
| Tainted var | copylen |
| Unvalidated size | copy_to_user() arg 2 line 1303 — size copylen |
| Sink snippet | if (copy_to_user(optval, mfi, copylen)) |
| Possibly guarded | no |
Dismissed: copylen in the caller is computed as min_t(unsigned int, len, sizeof(struct mptcp_info)), clamping any user-supplied length to sizeof(struct mptcp_info). After adding MIN_FULL_INFO_OPTLEN_SIZE, the maximum total is sizeof(struct mptcp_info) + MIN_FULL_INFO_OPTLEN_SIZE, which is within sizeof(struct mptcp_full_info) (the local stack struct). No counterexample exists: no user-supplied value can produce a copylen that exceeds the source buffer size. The scanner flagged it because the original len came from user space, but the min_t() clamp makes it safe.
mptcp_put_subflow_data() — net/mptcp/sockopt.c FP confidence=high
mptcp_put_subflow_data() computes copylen as min(sfd->size_subflow_data, sizeof(*sfd)). Even though size_subflow_data is user-supplied (read in mptcp_get_subflow_data()), the min_t() clamps copylen to at most sizeof(*sfd), which is a compile-time constant equal to the size of the source kernel struct. The copy_to_user() reads from a kernel stack buffer of exactly sizeof(*sfd) bytes, so no out-of-bounds kernel memory read is possible regardless of the user-supplied value.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 1074 |
| Taint snippet | if (copy_to_user(optval, sfd, copylen)) |
| Tainted var | copylen |
| Unvalidated size | copy_to_user() arg 2 line 1074 — size copylen |
| Sink snippet | if (copy_to_user(optval, sfd, copylen)) |
| Possibly guarded | no |
Dismissed: copylen = min_t(u32, sfd->size_subflow_data, sizeof(*sfd)). Even with a maximally adversarial user-supplied size_subflow_data (e.g., UINT32_MAX), copylen is clamped to sizeof(*sfd) by min_t. The copy_to_user() source is the kernel stack struct sfd of size sizeof(*sfd), so no kernel-side OOB read occurs. No counterexample can be constructed that bypasses min_t and causes OOB. False positive.
ip_set_sockfn_get() — net/netfilter/ipset/ip_set_core.c FP confidence=high
The function allocates `data = vmalloc(*len)` and saves `copylen = *len` at entry. Every code path that reaches the `copy:` label first validates `*len` with an exact equality check against the expected struct size (e.g., `if (*len != sizeof(struct ip_set_req_get_set))`), ensuring `copylen` equals the allocation size. The copy_to_user at line 2365 therefore cannot exceed the allocated buffer. The scanner flagged `copylen` as unvalidated, but the per-case exact-size guards are sufficient.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 2365 |
| Taint snippet | if (copy_to_user(user, data, copylen)) |
| Tainted var | copylen |
| Unvalidated size | copy_to_user() arg 2 line 2365 — size copylen |
| Sink snippet | if (copy_to_user(user, data, copylen)) |
| Possibly guarded | no |
Dismissed: copylen = *len at line 2259; vmalloc(*len) at line 2270 allocates exactly that many bytes. Every switch case reaching `copy:` enforces `*len == sizeof(specific_struct)` via exact equality, so copylen equals the buffer size at the copy_to_user call. No counterexample exists: any value of *len that passes a case's size check is also the exact allocation size. False positive.
hash_ip4_uadt() — net/netfilter/ipset/ip_set_hash_ip.c FP confidence=high
The function validates user-supplied netlink attributes properly before use. The loop at line 154 has a per-iteration counter check (i > IPSET_MAX_RANGE) that caps the maximum number of iterations regardless of the ip/ip_to values. The tainted 'ip' variable from ntohl(h->next.ip) is not used as a buffer index — it is an IP address value passed to adtfn(). Additionally, h->next.ip was written by the kernel itself in a prior call via hash_ip4_data_next(), making it internally computed rather than directly server-supplied from a network buffer.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 153 |
| Taint snippet | ip = ntohl(h->next.ip); |
| Tainted var | ip |
| Loop | for_loop line 154 |
| Sink snippet | for (; ip <= ip_to; i++) { |
| Possibly guarded | no |
Dismissed: The per-iteration guard 'if (i > IPSET_MAX_RANGE) return -ERANGE' at line 156 caps loop iterations at IPSET_MAX_RANGE+1 regardless of ip/ip_to values — criterion (b) is fully satisfied. No counterexample can be constructed: any ip <= ip_to range will be cut off by the iteration counter. Furthermore, h->next.ip is written by the kernel itself (hash_ip4_data_next sets it to a previously-validated e.ip value from a prior call), so it is not directly server-supplied from a network response. The variable 'ip' is also not used as a memory buffer index; it is an IP address value passed as a struct field to adtfn().
hash_ipmark4_uadt() — net/netfilter/ipset/ip_set_hash_ipmark.c FP confidence=high
The loop iterates over IPv4 addresses in a user-specified range, calling adtfn() per address. The tainted `ip` value from ntohl(h->next.ip) is a saved IP address (not a buffer index), and the loop has a per-iteration guard `if (i > IPSET_MAX_RANGE)` that limits iterations. No buffer is indexed by `ip`; there is no OOB memory access possible from this loop structure.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 152 |
| Taint snippet | ip = ntohl(h->next.ip); |
| Tainted var | ip |
| Loop | for_loop line 153 |
| Sink snippet | for (; ip <= ip_to; i++) { |
| Possibly guarded | no |
Dismissed: The scanner flagged `ip` controlling the loop iteration count, but `ip` here is an IPv4 address used only to drive iteration over an IP range — it is never used as a buffer index or allocation size. The per-iteration check `if (i > IPSET_MAX_RANGE)` (line 155) limits the total number of iterations regardless of the IP range size, providing sufficient protection against excessive looping. Counterexample construction: any value of `ip` from `h->next.ip` either exceeds `ip_to` (loop runs 0 times) or is within range (loop bounded by IPSET_MAX_RANGE). No memory corruption path exists.
hash_ipport4_uadt() — net/netfilter/ipset/ip_set_hash_ipport.c FP confidence=high
Both findings are false positives. First, h->next is kernel-internal state written by hash_ipport4_data_next() in a previous iteration — not a server-supplied network buffer. The ntohl()/ntohs() calls on h->next.ip and h->next.port are endian conversions of kernel-written values, not external data. Second, neither 'ip' nor 'p' index into a memory buffer; they iterate over address/port space calling adtfn(). Third, the inner loop body has an explicit IPSET_MAX_RANGE guard (line 195) that limits total iterations regardless of starting values. Finally, the outer loop is bounded by ip_to and the inner loop by port_to, both of which are u32/u16 range-limited values.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 188 |
| Taint snippet | ip = ntohl(h->next.ip); |
| Tainted var | ip |
| Loop | for_loop line 189 |
| Sink snippet | for (; ip <= ip_to;) { |
| Possibly guarded | no |
Dismissed: h->next.ip is written by hash_ipport4_data_next() with a kernel-computed htonl(ip) value. The outer loop is bounded by ip_to (a u32 ceiling). The inner loop has an IPSET_MAX_RANGE check. No buffer is indexed by ip — it's used only as a value to construct e.ip and call adtfn(). Cannot construct a counterexample where ip causes OOB access because no buffer indexing occurs.
Finding #2 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 190 |
| Taint snippet | p = retried && ip == ntohl(h->next.ip) ? ntohs(h->next.port) |
| Tainted var | p |
| Loop | for_loop line 192 |
| Sink snippet | for (; p <= port_to; p++, i++) { |
| Possibly guarded | no |
Dismissed: h->next.port is written by hash_ipport4_data_next() with a kernel-computed htons(p) value. The inner loop is bounded by port_to (u16 range). The IPSET_MAX_RANGE check at line 195 limits total iterations across all passes. p is used only as a value to construct e.port via htons(p) and call adtfn() — no buffer indexing occurs. Cannot construct a counterexample causing OOB.
hash_ipport6_uadt() — net/netfilter/ipset/ip_set_hash_ipport.c FP confidence=high
The function validates protocol attributes before the loop. The loop iterates over a port range (u16 values, max 65535 iterations) calling adtfn per port. The loop counter 'port' is never used as an array index or allocation size — it only controls how many times adtfn is called with a specific port value written to a local stack struct. h->next.port is internal kernel bookkeeping state (set when retrying), not a server-supplied network value. Even if adversarially controlled, u16 port values bound the loop to at most 65535 iterations with no memory safety impact.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 351 |
| Taint snippet | port = ntohs(h->next.port); |
| Tainted var | port |
| Loop | for_loop line 352 |
| Sink snippet | for (; port <= port_to; port++) { |
| Possibly guarded | no |
Dismissed: h->next.port is the kernel's own internal retry-state field, not a server/network-supplied value. More importantly, the loop counter 'port' is a u16-ranged port number that never indexes into any buffer or allocation — it only controls how many calls to adtfn are made, each operating on a local stack struct 'e'. The scanner misidentified port-range iteration as a buffer-bounds problem. No counterexample can be constructed that causes OOB memory access because no array/buffer indexing with 'port' exists.
hash_ipportip4_uadt() — net/netfilter/ipset/ip_set_hash_ipportip.c FP confidence=high
Both flagged taint sources (h->next.ip and h->next.port) are kernel-internally written values, set during a previous call when IPSET_MAX_RANGE was hit. They represent resume points, not network-supplied data. The loops do not index into a buffer — they call adtfn() with element values. The loops are bounded by ip<=ip_to and p<=port_to (validated ranges) plus the per-iteration IPSET_MAX_RANGE check on counter i. No OOB memory access is possible.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 184 |
| Taint snippet | ip = ntohl(h->next.ip); |
| Tainted var | ip |
| Loop | for_loop line 185 |
| Sink snippet | for (; ip <= ip_to;) { |
| Possibly guarded | no |
Dismissed: h->next.ip is written by the kernel itself (hash_ipportip4_data_next stores e.ip which was htonl(ip) where ip was already within the validated [ip,ip_to] range). The ntohl() here is purely an endian round-trip of a kernel-written value. The loop is bounded by ip<=ip_to. No counterexample can be constructed because ip is constrained to the already-validated ip_to bound.
Finding #2 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 186 |
| Taint snippet | p = retried && ip == ntohl(h->next.ip) ? ntohs(h->next.port) |
| Tainted var | p |
| Loop | for_loop line 188 |
| Sink snippet | for (; p <= port_to; p++, i++) { |
| Possibly guarded | no |
Dismissed: h->next.port is written by the kernel itself (stores e.port = htons(p) where p was within [port, port_to]). The ntohs() is a round-trip of a kernel-written value. The inner loop is bounded by p<=port_to (u16 range, max 65535) and the combined i>IPSET_MAX_RANGE guard. No counterexample can be constructed — p cannot exceed port_to which was validated earlier.
hash_ipportip6_uadt() — net/netfilter/ipset/ip_set_hash_ipportip.c FP confidence=high
The function validates inputs carefully with multiple early-return checks. The flagged loop iterates over port numbers (u16 values, naturally bounded to 0-65535). Both 'port' (from ntohs, u16 range) and 'port_to' (from ip_set_get_h16, u16 range) are inherently bounded. The loop body only writes to a stack-allocated struct field and calls a function pointer — no array indexing or pointer arithmetic using the port value. There is no OOB memory access possible from this loop, only at most 65536 iterations.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 346 |
| Taint snippet | port = ntohs(h->next.port); |
| Tainted var | port |
| Loop | for_loop line 347 |
| Sink snippet | for (; port <= port_to; port++) { |
| Possibly guarded | no |
Dismissed: The scanner flagged 'port' from ntohs(h->next.port) as controlling loop iteration count. However, ntohs() always returns a value in [0, 65535], and port_to is similarly bounded by ip_set_get_h16() to [0, 65535]. The loop can run at most 65536 times. More critically, the loop body does not index into any buffer using 'port' — it assigns to e.port (a stack field) and calls adtfn. No counterexample can be constructed where this causes OOB memory access. The scanner is treating 'loop iteration count' as dangerous regardless of whether the loop body actually performs bounded-memory operations. This is a false positive.
hash_ipportnet4_uadt() — net/netfilter/ipset/ip_set_hash_ipportnet.c FP confidence=high
All three flagged loops iterate over IP/port ranges to insert hash entries — they do NOT index into a buffer using the tainted values as offsets. The loop variables (ip, p, ip2) are used as values stored in the hash element 'e', not as array indices or pointer offsets. The IPSET_MAX_RANGE guard at line 286 limits total computational iterations across all three nested loops. The taint sources (h->next.ip, h->next.port, h->next.ip2) are kernel-internal resume-state values saved by hash_ipportnet4_data_next() from a prior kernel execution, not raw server/network buffer data used for memory addressing. No buffer bounds issue exists.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 270 |
| Taint snippet | ip = ntohl(h->next.ip); |
| Tainted var | ip |
| Loop | for_loop line 277 |
| Sink snippet | for (; ip <= ip_to;) { |
| Possibly guarded | no |
Dismissed: h->next.ip is a kernel-internal resume cursor saved by hash_ipportnet4_data_next() in a prior invocation. The loop 'for (; ip <= ip_to;)' does not index a buffer — it calls adtfn() with an htonl(ip) value stored in element 'e'. The upper bound ip_to is separately validated. IPSET_MAX_RANGE (line 286) caps total work. No counterexample for OOB access can be constructed because no buffer is indexed by ip.
Finding #2 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 249 |
| Taint snippet | port_to = port = ntohs(e.port); |
| Tainted var | p |
| Loop | for_loop line 279 |
| Sink snippet | for (; p <= port_to; p++) { |
| Possibly guarded | no |
Dismissed: h->next.port is a kernel-internal resume cursor. The port loop 'for (; p <= port_to; p++)' stores p as htons(p) in e.port and calls adtfn() — no buffer indexing. port_to is bounded by u16 range (0-65535). IPSET_MAX_RANGE caps total iterations. No OOB counterexample possible.
Finding #3 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 272 |
| Taint snippet | ip2 = ntohl(h->next.ip2); |
| Tainted var | ip2 |
| Loop | do_loop line 281 |
| Sink snippet | do { |
| Possibly guarded | no |
Dismissed: h->next.ip2 is a kernel-internal resume cursor. The do-while loop uses ip2 as a value stored in e.ip2 via htonl(ip2) — no buffer indexing. The loop condition 'ip2++ < ip2_to' and the IPSET_MAX_RANGE guard (line 286) bound execution. The check at line 263 'if (ip2_from + UINT_MAX == ip2_to)' prevents a full 32-bit wraparound range. No OOB counterexample possible.
hash_ipportnet6_uadt() — net/netfilter/ipset/ip_set_hash_ipportnet.c FP confidence=high
The function validates protocol attributes and CIDR ranges carefully. The flagged loop iterates port numbers (u16 range, max 65535) from 'port' to 'port_to', both of which are bounded by 16-bit arithmetic. No buffer is indexed by the port value inside the loop — adtfn() is called with a struct element containing the port number. There is no OOB memory access possible regardless of the port value.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 504 |
| Taint snippet | port = ntohs(h->next.port); |
| Tainted var | port |
| Loop | for_loop line 505 |
| Sink snippet | for (; port <= port_to; port++) { |
| Possibly guarded | no |
Dismissed: h->next.port is internal kernel ipset state (the resume point for retried operations), not directly server-supplied. Even treating it as tainted: both 'port' and 'port_to' are u16 values (ntohs/ip_set_get_h16 return 16-bit port numbers, max 65535). The loop body uses 'port' only as a port number value stored in e.port via htons(), not as an array index or allocation size. No buffer is accessed with 'port' as an offset. The maximum loop count is 65535 iterations, which is the normal ipset behavior for port ranges. No counterexample can be constructed that causes OOB memory access — the loop is a performance concern at most, not a memory safety issue.
hash_net4_uadt() — net/netfilter/ipset/ip_set_hash_net.c FP confidence=high
The function validates CIDR values, IP ranges, and critically has an explicit per-iteration counter (i > IPSET_MAX_RANGE) inside the loop that bounds total iterations regardless of the starting ip value. The tainted 'ip' from ntohl(h->next.ip) is internally computed kernel state from a previous invocation, not a raw network-supplied value, and does not index any buffer directly.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 193 |
| Taint snippet | ip = ntohl(h->next.ip); |
| Tainted var | ip |
| Loop | do_loop line 194 |
| Sink snippet | do { |
| Possibly guarded | no |
Dismissed: The loop contains an explicit iteration counter 'i' that triggers early exit via 'if (i > IPSET_MAX_RANGE) return -ERANGE' — this is a textbook per-iteration bounds check (form b). No counterexample exists: regardless of the value of 'ip' from ntohl(h->next.ip), the loop cannot execute more than IPSET_MAX_RANGE+1 times. Furthermore, 'ip' does not index any array or buffer; it is used only as an IP address value passed to htonl() and ip_set_range_to_cidr(). The h->next.ip value is set by the kernel itself in a prior invocation via hash_net4_data_next(), making it internally computed state rather than a direct server-supplied network buffer read.
hash_netiface4_uadt() — net/netfilter/ipset/ip_set_hash_netiface.c FP confidence=high
The function iterates over an IP address range, not a memory buffer. The tainted value 'ip' from ntohl(h->next.ip) is used as an IP address, not as an array index or memory offset. A per-iteration counter check (i > IPSET_MAX_RANGE) caps the number of loop iterations regardless of the ip value. No memory buffer is indexed by 'ip', so no OOB memory access is possible.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 261 |
| Taint snippet | ip = ntohl(h->next.ip); |
| Tainted var | ip |
| Loop | do_loop line 262 |
| Sink snippet | do { |
| Possibly guarded | no |
Dismissed: h->next.ip is kernel-maintained state set by hash_netiface4_data_next() during a prior (non-retried) invocation of this same function, where ip was already user-validated. More importantly, 'ip' is used as an IP address value (not a memory index), and the do-while loop has an explicit per-iteration guard 'if (i > IPSET_MAX_RANGE) return -ERANGE' that caps iterations regardless of the ip start value. No counterexample exists where ip causes OOB memory access: the loop body passes ip as a value to adtfn, not as an array offset. The scanner confused IP range iteration with buffer indexing.
hash_netnet4_uadt() — net/netfilter/ipset/ip_set_hash_netnet.c FP confidence=high
The loops in hash_netnet4_uadt iterate over IP address ranges, not array indices. The variables 'ip' and 'ip2' are u32 IP addresses used as loop counters compared against 'ip_to'/'ip2_to' bounds. No buffer is indexed by these values. The inner loop has a hard cap via 'i > IPSET_MAX_RANGE' that bounds total iterations regardless of starting IP values. Even if 'ip' and 'ip2' are loaded from h->next (which itself was written from prior iterations of the same loop), no OOB memory access is possible. The scanner incorrectly categorized IP-range iteration as unbounded buffer traversal.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 254 |
| Taint snippet | ip = ntohl(h->next.ip[0]); |
| Tainted var | ip |
| Loop | do_loop line 260 |
| Sink snippet | do { |
| Possibly guarded | no |
Dismissed: h->next.ip[0] is a kernel-internal value written by hash_netnet4_data_next() in a prior iteration of this same function. The value is an IP address used as a loop starting point, compared against ip_to (also user-supplied but bounded the same way). The loop does not index any memory buffer with 'ip'; it calls adtfn() per CIDR block. The IPSET_MAX_RANGE check at line 266 caps total iterations. No counterexample for OOB exists because no array indexing occurs.
Finding #2 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 255 |
| Taint snippet | ip2 = ntohl(h->next.ip[1]); |
| Tainted var | ip2 |
| Loop | do_loop line 263 |
| Sink snippet | do { |
| Possibly guarded | no |
Dismissed: h->next.ip[1] is similarly a kernel-internal value (set by hash_netnet4_data_next from e.ip[1]=htonl(ip2) in a prior call). Used as inner loop starting point against ip2_to. The IPSET_MAX_RANGE check at line 266 bounds total inner+outer iterations combined. No array buffer is indexed by ip2. No counterexample for OOB exists.
hash_netport4_uadt() — net/netfilter/ipset/ip_set_hash_netport.c FP confidence=high
The loops iterate over IP and port ranges to insert entries into a hash set via adtfn(). They do not perform indexed memory buffer access — no pointer arithmetic with tainted values against a received buffer. Both loops are protected by a per-iteration counter check 'if (i > IPSET_MAX_RANGE)' at line 251 which covers the combined iteration count across both loops, satisfying protection condition (b). Additionally, port values are inherently bounded to 16-bit (0–65535), and IP range wraparound is blocked by the 'ip + UINT_MAX == ip_to' check at line 233. The scanner is pattern-matching loop iteration counts as dangerous but the actual sink is a hash-set insertion function, not a memory buffer dereference.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 240 |
| Taint snippet | ip = ntohl(h->next.ip); |
| Tainted var | ip |
| Loop | do_loop line 245 |
| Sink snippet | do { |
| Possibly guarded | no |
Dismissed: The outer do-while loop iterates over IP addresses from 'ip' to 'ip_to'. The per-iteration check 'i > IPSET_MAX_RANGE' at line 251 accumulates across all inner and outer iterations and returns -ERANGE when exceeded. No buffer is indexed with 'ip' — it is stored into e.ip via htonl(ip). Counterexample attempt: even if ip=0 and ip_to=0xFFFFFFFF, the IPSET_MAX_RANGE check fires after at most IPSET_MAX_RANGE+1 total adtfn() calls. No OOB memory access is possible.
Finding #2 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 221 |
| Taint snippet | port = port_to = ntohs(e.port); |
| Tainted var | p |
| Loop | for_loop line 249 |
| Sink snippet | for (; p <= port_to; p++, i++) { |
| Possibly guarded | no |
Dismissed: The inner for-loop iterates 'p' from port to port_to. Both values are derived from ntohs() of 16-bit network attributes, so they are inherently bounded to [0, 65535] — the maximum loop count from ports alone is 65536. Combined with the 'i > IPSET_MAX_RANGE' per-iteration check, the total iterations are strictly bounded. The value 'p' is only used as htons(p) stored into e.port — no buffer indexing occurs. No counterexample exists that causes OOB access.
hash_netport6_uadt() — net/netfilter/ipset/ip_set_hash_netport.c FP confidence=high
The function validates CIDR, protocol, and port attributes from netlink. The flagged loop iterates over a range of port numbers (u16 values, 0-65535), calling adtfn() for each port. This is not a buffer traversal — 'port' is used as a port number value passed to adtfn(), not as a memory index or allocation size. The loop is bounded by u16 semantics (max 65536 iterations) and cannot cause OOB memory access.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 447 |
| Taint snippet | port = ntohs(h->next.port); |
| Tainted var | port |
| Loop | for_loop line 448 |
| Sink snippet | for (; port <= port_to; port++) { |
| Possibly guarded | no |
Dismissed: The scanner misidentifies this as a buffer bounds problem. 'port' is a u16 port number (0-65535) used as a value argument to adtfn(), not as a buffer index. The loop iterates over a range of port numbers, with both 'port' and 'port_to' naturally bounded to [0, 65535] by their u16 origins (ntohs() of __be16 and ip_set_get_h16()). No counterexample exists: any values of port/port_to in [0,65535] will cause the loop to terminate in at most 65536 iterations without any OOB access. The retried path sets port=ntohs(h->next.port) which is also a u16 value. This is a false positive.
hash_netportnet4_uadt() — net/netfilter/ipset/ip_set_hash_netportnet.c FP confidence=high
The scanner misidentifies these loops as buffer-indexing loops. The loop variables (ip, ip2, p) are IP addresses and port numbers used to enumerate set members for hash operations — not buffer indices or allocation sizes. The loop termination is controlled by ip_to/ip2_to/port_to which are independently derived from validated netlink attributes. Additionally, the inner loop has an explicit IPSET_MAX_RANGE guard (line 318) that counts total iterations across all three loops and returns -ERANGE if exceeded, providing robust per-iteration protection. No OOB memory access is possible from these values.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 302 |
| Taint snippet | ip = ntohl(h->next.ip[0]); |
| Tainted var | ip |
| Loop | do_loop line 310 |
| Sink snippet | do { |
| Possibly guarded | no |
Dismissed: ip from h->next.ip[0] is a kernel-internal retry cursor (written by hash_netportnet4_data_next in a previous call), not a server-supplied value. It is used as a loop starting point, not an iteration count or buffer index. The outer do-while terminates when ip >= ip_to (validated from netlink attr). IPSET_MAX_RANGE bounds total iterations. No counterexample for OOB can be constructed because no buffer is indexed.
Finding #2 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 281 |
| Taint snippet | port_to = port = ntohs(e.port); |
| Tainted var | p |
| Loop | for_loop line 313 |
| Sink snippet | for (; p <= port_to; p++) { |
| Possibly guarded | no |
Dismissed: p comes from ntohs(e.port) where e.port is a u16 port number from a validated netlink attribute (nla_get_be16). Port values are bounded to [0,65535] by the u16 type. The for loop p<=port_to where both are u32 with values ≤65535 is safe. IPSET_MAX_RANGE in the inner loop further limits total iterations. No buffer indexing occurs.
Finding #3 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 304 |
| Taint snippet | ip2 = ntohl(h->next.ip[1]); |
| Tainted var | ip2 |
| Loop | do_loop line 315 |
| Sink snippet | do { |
| Possibly guarded | no |
Dismissed: ip2 from h->next.ip[1] is a kernel-internal retry cursor, not a server-supplied value. Used as loop starting point; inner do-while terminates when ip2 >= ip2_to (validated from netlink attr). The explicit IPSET_MAX_RANGE check at line 318 fires before any adtfn call when the total iteration count exceeds the limit, returning -ERANGE. No memory buffer is indexed by ip2.
hash_netportnet6_uadt() — net/netfilter/ipset/ip_set_hash_netportnet.c FP confidence=high
This function processes user/netlink-supplied attributes with proper validation of IP addresses, CIDRs, protocols, and port values. The flagged loop iterates over port numbers (not buffer indices), calling adtfn for each port in a range. The value h->next.port is kernel-internal state tracking retry position, not a server/network-supplied value. The loop has a natural upper bound (port <= port_to) and does not use port as a buffer index.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 561 |
| Taint snippet | port = ntohs(h->next.port); |
| Tainted var | port |
| Loop | for_loop line 562 |
| Sink snippet | for (; port <= port_to; port++) { |
| Possibly guarded | no |
Dismissed: h->next.port is kernel-internal state (resume point for retried operations), not a value received from a network packet or server response. The loop iterates over u16 port numbers (naturally bounded to 0-65535) using the condition 'port <= port_to', where port_to is itself bounded to 65535. No buffer is indexed by port; the only use of port in the loop body is htons(port) assigned to e.port. No counterexample can be constructed because: (1) the value is not server-supplied, (2) even if it were, if h->next.port > port_to, the loop body never executes, and (3) even if the loop runs many iterations, it only calls adtfn repeatedly with valid e.port values — no OOB memory access occurs.
do_ip_vs_get_ctl() — net/netfilter/ipvs/ip_vs_ctl.c FP confidence=high
The flagged copy_to_user() call uses strlen(buf)+1 as its size argument, where 'buf' is a kernel-local stack buffer of 64 bytes filled by sprintf() with a fixed format string. The content is entirely kernel-controlled (version constants and a table size value from kernel internals), not user-supplied or server-supplied. The taint source labeling is incorrect — the scanner appears to have flagged strlen() on a kernel-internal buffer as if it were user-controlled. The size is bounded by the format string and kernel constants, making it impossible for strlen(buf)+1 to exceed 64. All other cases (GET_SERVICES, GET_DESTS) properly validate the user-supplied *len against the computed struct_size() before use.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 3820 |
| Taint snippet | if (copy_to_user(user, buf, strlen(buf)+1) != 0) { |
| Tainted var | strlen(buf)+1 |
| Unvalidated size | copy_to_user() arg 2 line 3820 — size strlen(buf)+1 |
| Sink snippet | if (copy_to_user(user, buf, strlen(buf)+1) != 0) { |
| Possibly guarded | no |
Dismissed: The 'buf' variable is a 64-byte kernel stack buffer filled with sprintf() using a fixed format string containing only kernel-internal constants (IP_VS_VERSION_CODE and get_conn_tab_size()). strlen(buf)+1 is therefore bounded by the format output length, which is well under 64 bytes. No user-supplied data influences this size. The taint tracking appears to have been confused by the local strlen() call. No bounds check is needed here because the size is kernel-controlled. Counterexample: no concrete numeric values from user input can influence strlen(buf)+1 because the buffer content is entirely determined by kernel constants written by sprintf().
set_sctp_state() — net/netfilter/ipvs/ip_vs_proto_sctp.c FP confidence=high
The scanner incorrectly propagates taint from ntohs(sch->length) (used for chunk offset arithmetic) to sch->type (a separate u8 field in sctp_chunkhdr) and then through kernel-defined lookup tables. Key protections: (1) skb_header_pointer validates packet bounds and returns NULL if out of range; (2) the NULL check guards sch->type dereference; (3) chunk_type is a u8 field, not a 16-bit truncation; (4) chunk_type is bounds-checked before array use at line 415; (5) event and next_state come from kernel-defined constant tables, not raw server values; (6) sctp_state_name() has its own bounds check at line 369.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 405 |
| Taint snippet | int clen = ntohs(sch->length); |
| Tainted var | sch |
| Pointer deref | sch->type line 410 |
| Sink snippet | if (sch && sch->type == SCTP_CID_ABORT) |
| Possibly guarded | no |
Dismissed: skb_header_pointer() validates that cofs+ALIGN(clen,4) through cofs+ALIGN(clen,4)+sizeof(_sctpch) lies within the skb data; if not, it returns NULL. The explicit NULL check 'if (sch && sch->type == SCTP_CID_ABORT)' at line 410 guards the dereference. No counterexample is constructable: any offset that would go OOB causes skb_header_pointer to return NULL, which is checked.
Finding #2 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohs() line 411 |
| Taint snippet | chunk_type = sch->type; |
| Tainted var | chunk_type |
| Truncation | line 411: 16 → 8-bit u8 |
| Sink snippet | chunk_type = sch->type; |
| Possibly guarded | yes (heuristic) |
Dismissed: struct sctp_chunkhdr has 'type' as __u8, not a 16-bit value. The scanner incorrectly associates the ntohs() taint from sch->length with sch->type, which is a completely separate 8-bit field. There is no 16-to-8-bit truncation here. False positive due to incorrect taint propagation.
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 405 |
| Taint snippet | int clen = ntohs(sch->length); |
| Tainted var | sch |
| Pointer deref | sch->type line 411 |
| Sink snippet | chunk_type = sch->type; |
| Possibly guarded | yes (heuristic) |
Dismissed: Same reasoning as finding #1. sch at line 411 is guarded by the condition 'if (sch && sch->type == SCTP_CID_ABORT)' at line 410, so chunk_type = sch->type at line 411 is only reached when sch is non-NULL (i.e., skb_header_pointer validated the bounds). False positive.
Finding #4 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 405 |
| Taint snippet | int clen = ntohs(sch->length); |
| Tainted var | chunk_type |
| Subscript | [] line 416 |
| Sink snippet | sctp_events[chunk_type] : IP_VS_SCTP_DATA; |
| Possibly guarded | no |
Dismissed: chunk_type is a u8 (0-255). Line 415 explicitly checks 'chunk_type < sizeof(sctp_events)' before using it as an index. sizeof(sctp_events) returns the byte size of the array which equals the number of elements (each being one byte if u8, or larger if int — either way the check is 'chunk_type < number_of_elements'). No counterexample exists: any chunk_type >= sizeof(sctp_events) takes the else branch returning IP_VS_SCTP_DATA. False positive.
Finding #5 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 405 |
| Taint snippet | int clen = ntohs(sch->length); |
| Tainted var | event |
| Subscript | [] line 428 |
| Sink snippet | next_state = sctp_states[direction][event][cp->state]; |
| Possibly guarded | no |
Dismissed: event is the result of a lookup in the kernel-defined sctp_events[] table (or IP_VS_SCTP_DATA constant). These are kernel-internal constants, not server-supplied values. The taint chain through ntohs()->chunk_type->sctp_events[chunk_type] ends at a kernel constant. The sctp_states table dimensions match the defined event constants by construction. False positive.
Finding #6 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 405 |
| Taint snippet | int clen = ntohs(sch->length); |
| Tainted var | next_state |
| Subscript | [] line 462 |
| Sink snippet | cp->timeout = pd->timeout_table[cp->state = next_state]; |
| Possibly guarded | no |
Dismissed: next_state is derived from sctp_states[direction][event][cp->state], a kernel-defined 3D constant table. Its values are IP_VS_SCTP_S_* enum constants guaranteed to be within the timeout_table bounds by kernel construction. Not a server-supplied value in any meaningful sense. False positive.
Finding #7 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 405 |
| Taint snippet | int clen = ntohs(sch->length); |
| Tainted var | next_state |
| Subscript | [] line 464 |
| Sink snippet | cp->timeout = sctp_timeouts[cp->state = next_state]; |
| Possibly guarded | no |
Dismissed: Same as finding #6. next_state from kernel constant table used to index sctp_timeouts[], another kernel-defined array sized to IP_VS_SCTP_S_LAST. False positive.
Finding #8 — Category C — cross-function via sctp_state_name() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 405 |
| Taint snippet | int clen = ntohs(sch->length); |
| Tainted var | next_state |
| Call site | line 443 — passes next_state to sctp_state_name() |
| Call snippet | sctp_state_name(next_state), |
| Subscript (in callee) | [] line 371 |
| Sink snippet | if (sctp_state_name_table[state]) |
| Possibly guarded | yes (heuristic) |
Dismissed: next_state is a kernel table constant (not server-supplied). Additionally, sctp_state_name() itself guards with 'if (state >= IP_VS_SCTP_S_LAST) return ERR!' before any array access (line 369-370). Both the value origin (kernel constant) and the callee's own validation make this a false positive.
Finding #9 — Category C — cross-function via sctp_state_name() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 405 |
| Taint snippet | int clen = ntohs(sch->length); |
| Tainted var | next_state |
| Call site | line 443 — passes next_state to sctp_state_name() |
| Call snippet | sctp_state_name(next_state), |
| Subscript (in callee) | [] line 372 |
| Sink snippet | return sctp_state_name_table[state]; |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #8. sctp_state_name() validates state >= IP_VS_SCTP_S_LAST before accessing sctp_state_name_table[state] at line 372. The guard at line 369 covers both array accesses at lines 371 and 372. False positive.
ip_vs_proc_sync_conn() — net/netfilter/ipvs/ip_vs_sync.c FP confidence=high
ip_vs_proc_sync_conn() applies careful input validation on the server-supplied state value before passing it to ip_vs_proc_conn(). For non-template connections, state is validated against pp->num_states and the function returns early on failure. For template connections, the array subscript sink in ip_vs_proc_conn() is guarded by !(flags & IP_VS_CONN_F_TEMPLATE), so the template path takes the else branch (fixed timeout) and never indexes into timeout_table. The two guards together make OOB access impossible.
Finding #1 — Category C — cross-function via ip_vs_proc_conn() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 1149 |
| Taint snippet | state = ntohs(s->v4.state); |
| Tainted var | state |
| Call site | line 1177 — passes state to ip_vs_proc_conn() |
| Call snippet | ip_vs_proc_conn(ipvs, ¶m, flags, state, s->v4.protocol, af, |
| Subscript (in callee) | [] line 948 |
| Sink snippet | cp->timeout = pd->timeout_table[state]; |
| Possibly guarded | yes (heuristic) |
Dismissed: Two complementary guards prevent OOB: (1) for non-template connections, ip_vs_proc_sync_conn() checks state >= pp->num_states and returns retc=40 before calling ip_vs_proc_conn(); (2) the sink in ip_vs_proc_conn() at line 948 is inside 'if (!(flags & IP_VS_CONN_F_TEMPLATE) && pd && pd->timeout_table)', so template connections never reach the array indexing. No counterexample could be constructed — every path with an unchecked state value skips the array subscript sink. False positive.
ip_vs_process_message() — net/netfilter/ipvs/ip_vs_sync.c FP confidence=high
ip_vs_process_message() has a layered validation discipline: it checks that msg_end (= p + size) does not exceed buffer+buflen (line 1245) before passing it to ip_vs_proc_sync_conn(). The callee itself further validates all pointer arithmetic against msg_end before any dereference (lines 1098, 1106, 1111). No counterexample can be constructed that passes the caller's guard yet causes OOB in the callee.
Finding #1 — Category F — cross-function via ip_vs_proc_sync_conn() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 1242 |
| Taint snippet | size = ntohs(s->v4.ver_size) & SVER_MASK; |
| Tainted var | msg_end |
| Call site | line 1255 — passes msg_end to ip_vs_proc_sync_conn() |
| Call snippet | retc = ip_vs_proc_sync_conn(ipvs, p, msg_end); |
| Loop | while_loop line 1102 |
| Sink snippet | while (p < msg_end) { |
| Possibly guarded | yes (heuristic) |
Dismissed: Line 1245 ensures msg_end <= buffer+buflen before the call. Inside ip_vs_proc_sync_conn(), msg_end is used only as a loop/comparison bound with callee-internal sub-checks (p>msg_end at line 1098, p+2>msg_end at line 1106, p+plen>msg_end at line 1111). No counterexample exists: any size value that passes the caller's guard produces a msg_end within the buffer, and callee guards catch undersized structs. The finding is a false positive.
ip_vs_process_message_v0() — net/netfilter/ipvs/ip_vs_sync.c FP confidence=high
ip_vs_process_message_v0() validates state against pp->num_states for non-template connections and skips (continue) any connection with out-of-range state before calling ip_vs_proc_conn(). The sink in ip_vs_proc_conn() at line 948 is further guarded by !(flags & IP_VS_CONN_F_TEMPLATE), which matches the same condition under which the caller validated state. No counterexample can be constructed that passes the caller's guard yet causes OOB at the sink.
Finding #1 — Category C — cross-function via ip_vs_proc_conn() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 992 |
| Taint snippet | state = ntohs(s->state); |
| Tainted var | state |
| Call site | line 1018 — passes state to ip_vs_proc_conn() |
| Call snippet | ip_vs_proc_conn(ipvs, ¶m, flags, state, s->protocol, AF_INET, |
| Subscript (in callee) | [] line 948 |
| Sink snippet | cp->timeout = pd->timeout_table[state]; |
| Possibly guarded | yes (heuristic) |
Dismissed: For non-template connections, the caller checks state >= pp->num_states and continues (skipping ip_vs_proc_conn()) if out of range. The sink pd->timeout_table[state] in ip_vs_proc_conn() is guarded by !(flags & IP_VS_CONN_F_TEMPLATE), exactly the condition under which the caller already validated state < pp->num_states. No counterexample exists: any state value that would reach line 948 has already been bounds-checked. False positive.
conntrack_pptp_help() — net/netfilter/nf_conntrack_pptp.c FP confidence=high
The function carefully validates network-supplied values before use. The flagged array access at line 498 is protected by explicit bounds checks (msg > 0 && msg <= PPTP_MSG_MAX) in the same conditional expression, using C short-circuit evaluation semantics. The static scanner failed to recognize these guards as sufficient because they appear in the same expression as the subscript rather than in a preceding statement.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 497 |
| Taint snippet | msg = ntohs(ctlh->messageType); |
| Tainted var | msg |
| Subscript | [] line 498 |
| Sink snippet | if (msg > 0 && msg <= PPTP_MSG_MAX && reqlen < pptp_msg_size[msg]) |
| Possibly guarded | no |
Dismissed: The expression 'if (msg > 0 && msg <= PPTP_MSG_MAX && reqlen < pptp_msg_size[msg])' uses C short-circuit evaluation: pptp_msg_size[msg] is only evaluated when both msg > 0 and msg <= PPTP_MSG_MAX are true, constraining msg to [1, PPTP_MSG_MAX]. No counterexample can be constructed — any msg value that reaches the array access is already bounded within the valid array index range. The scanner incorrectly reported 'possibly guarded: no', when in fact the guards are in the same conditional expression and are effective.
hash_by_src() — net/netfilter/nf_nat_core.c FP confidence=high
The function implements NAT port selection. The 'range' parameter comes from kernel NAT rule configuration (iptables/nftables rules set by the administrator), not directly from network packet data. Even treating the values as tainted, the loop controlled by 'attempts' does not index into any buffer - it iterates over port number candidates calling nf_nat_used_tuple_harder(). The attempts variable is also hard-capped at NF_NAT_MAX_ATTEMPTS before the loop. No OOB memory access is possible from this flow.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 643 |
| Taint snippet | max = ntohs(range->max_proto.all); |
| Tainted var | attempts |
| Loop | for_loop line 669 |
| Sink snippet | for (i = 0; i < attempts; i++, off++) { |
| Possibly guarded | no |
Dismissed: The 'range' struct comes from NAT rule configuration (administrator-controlled), not from network packets. The loop body computes 'min + off % range_size' for port selection — no buffer is indexed by 'attempts' or 'i'. The 'attempts' variable is explicitly capped at NF_NAT_MAX_ATTEMPTS (lines 659-660) before the loop. No counterexample for OOB access can be constructed because there is no buffer being traversed — only port number arithmetic and a lookup call. False positive.
nf_tables_delchain() — net/netfilter/nf_tables_api.c FP confidence=high
The scanner has made a fundamental attribution error. The taint source is reported as be64_to_cpu() at line 3314 (reading a handle from a netlink attribute), but the flagged sink at line 3351 is 'use = chain->use' — this reads chain->use, which is an internal kernel reference counter maintained by the netlink/nftables subsystem, not derived from the be64_to_cpu() result. The handle was used only to look up the chain object via nft_chain_lookup_byhandle(); after that lookup, the chain pointer is an internal kernel object whose 'use' field is managed by the kernel. The taint propagation from be64_to_cpu() through the chain lookup to chain->use is a false positive — the scanner has followed the pointer through the lookup function and incorrectly attributed the internal reference count as tainted by user input. Additionally, chain->use is a u32 field in struct nft_chain, not a 64-bit value, so there is no truncation occurring.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | be64_to_cpu() line 3351 |
| Taint snippet | use = chain->use; |
| Tainted var | use |
| Truncation | line 3351: 64 → 32-bit u32 |
| Sink snippet | use = chain->use; |
| Possibly guarded | yes (heuristic) |
Dismissed: The taint path is incorrect. be64_to_cpu() at line 3314 converts a user-supplied chain handle (a u64 lookup key). This handle is passed to nft_chain_lookup_byhandle(), which searches the kernel's internal chain list and returns a pointer to a kernel-managed struct nft_chain. The assignment 'use = chain->use' at line 3351 reads the kernel-internal reference counter field (u32) from this struct — it is not influenced by the user-supplied handle value beyond selecting which chain to look at. No truncation of a 64-bit user value occurs. The check at line 3347-3349 (chain->use > 0) and the subsequent decrement loop provide correct internal logic. This is a false positive caused by overly aggressive taint propagation through a pointer-lookup function.
nf_tables_newchain() — net/netfilter/nf_tables_api.c FP confidence=high
Both findings are false positives. Finding #1: the policy value from ntohl() is explicitly validated in a switch statement that only accepts NF_DROP and NF_ACCEPT before being assigned to the u8 variable — any other 32-bit value returns -EINVAL, so no truncation surprise is possible. Finding #2: the taint chain is misattributed — chain->flags is a kernel-internal field of type u8 (defined in struct nft_chain), not derived from be64_to_cpu(). The be64_to_cpu() call produces the 'handle' used in nft_chain_lookup_byhandle(), not chain->flags. The scanner incorrectly propagated taint from handle through the chain pointer to chain->flags.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohl() line 3169 |
| Taint snippet | policy = ntohl(nla_get_be32(nla[NFTA_CHAIN_POLICY])); |
| Tainted var | policy |
| Truncation | line 3169: 32 → 8-bit u8 |
| Sink snippet | policy = ntohl(nla_get_be32(nla[NFTA_CHAIN_POLICY])); |
| Possibly guarded | no |
Dismissed: The 32-bit value from ntohl() is validated via a switch statement at lines 3170-3176 that only permits NF_DROP (1) and NF_ACCEPT (0), returning -EINVAL for any other value. No counterexample exists: any 32-bit value that is not 0 or 1 is rejected before assignment, so the truncation to u8 is safe. The switch check comes BEFORE the assignment completes its effect is used, and the only values that survive are within u8 range.
Finding #2 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | be64_to_cpu() line 3182 |
| Taint snippet | flags = chain->flags; |
| Tainted var | flags |
| Truncation | line 3182: 64 → 32-bit u32 |
| Sink snippet | flags = chain->flags; |
| Possibly guarded | yes (heuristic) |
Dismissed: The scanner incorrectly traces taint from be64_to_cpu() (line 3136, producing 'handle') through the nft_chain_lookup_byhandle() call to 'chain', and then to chain->flags at line 3182. However, chain->flags is a kernel-internal struct field populated by the kernel itself when the chain was created — it is not derived from the network-supplied handle value. The handle is merely a lookup key; the returned chain pointer points to a kernel object whose flags field is set by nft_chain_init() or similar kernel code. This is a classic false positive: taint laundered through a pointer dereference into a kernel-owned structure. Additionally, chain->flags is typed as u8 in struct nft_chain, so even if it were tainted, assigning a u8 to a u32 is a widening (zero-extension), not a truncation.
nf_tables_newobj() — net/netfilter/nf_tables_api.c FP confidence=high
The scanner propagates taint from `objtype` (user-supplied) through `nft_obj_type_get()` into `type`, but `nft_obj_type_get()` is a kernel registry lookup — it either returns ERR_PTR (checked at line 8359) or a pointer to a statically-registered kernel-internal `nft_object_type` struct. The fields `type->maxattr`, `type->policy`, `type->ops`, and `ops->size` are kernel constants defined at module registration time, not user-controlled values. The IS_ERR() guard at line 8359 is both necessary and sufficient to ensure `type` is a valid kernel pointer before use.
Finding #1 — Category B — cross-function via nft_obj_init() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohl() line 8322 |
| Taint snippet | objtype = ntohl(nla_get_be32(nla[NFTA_OBJ_TYPE])); |
| Tainted var | type |
| Call site | line 8364 — passes type to nft_obj_init() |
| Call snippet | obj = nft_obj_init(&ctx, type, nla[NFTA_OBJ_DATA]); |
| Sink (in callee) | memset() line 8172 (arg 2, role=size_mul_overflow) |
| Sink snippet | memset(tb, 0, sizeof(tb[0]) * (type->maxattr + 1)); |
| Possibly guarded | no |
Dismissed: type->maxattr is a field of a kernel-registered nft_object_type struct, not user data. nft_obj_type_get() acts as a validation gate returning a trusted kernel pointer or ERR_PTR. The IS_ERR check at line 8359 guards all subsequent uses. The memset size (type->maxattr+1) is entirely kernel-controlled. No counterexample possible — user cannot influence the value of maxattr in a registered type.
Finding #2 — Category B — cross-function via nft_obj_init() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohl() line 8322 |
| Taint snippet | objtype = ntohl(nla_get_be32(nla[NFTA_OBJ_TYPE])); |
| Tainted var | type |
| Call site | line 8364 — passes type to nft_obj_init() |
| Call snippet | obj = nft_obj_init(&ctx, type, nla[NFTA_OBJ_DATA]); |
| Sink (in callee) | kzalloc() line 8186 (arg 0, role=size_mul_overflow) |
| Sink snippet | obj = kzalloc(sizeof(*obj) + ops->size, GFP_KERNEL_ACCOUNT); |
| Possibly guarded | no |
Dismissed: ops->size is a field of a kernel-registered nft_object_ops struct (reachable via type->ops or type->select_ops). User cannot influence this value. The kzalloc size (sizeof(*obj) + ops->size) uses only kernel-defined constants. No counterexample possible.
Finding #3 — Category E — cross-function via nft_obj_init() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 8322 |
| Taint snippet | objtype = ntohl(nla_get_be32(nla[NFTA_OBJ_TYPE])); |
| Tainted var | type |
| Call site | line 8364 — passes type to nft_obj_init() |
| Call snippet | obj = nft_obj_init(&ctx, type, nla[NFTA_OBJ_DATA]); |
| Pointer deref | type-> line 8194 |
| Sink snippet | obj->ops = ops; |
| Possibly guarded | no |
Dismissed: obj->ops = ops is an assignment to a freshly kzalloc'd object (checked at line 8187). obj is a valid kernel allocation at this point. ops is derived from kernel-registered type struct, not user data. This is normal initialization, not a vulnerable dereference.
nf_tables_newrule() — net/netfilter/nf_tables_api.c FP confidence=high
All findings are false positives caused by the scanner incorrectly propagating taint from `handle = be64_to_cpu(nla_get_be64(nla[NFTA_RULE_HANDLE]))` through `__nft_rule_lookup()` to a completely separate `rule` pointer that is freshly allocated by `kzalloc()` at line 4438. The newly allocated `rule` is a kernel-local buffer with no relationship to the server-supplied handle value. Similarly, `size` is bounded by the `if (size >= 1 << 12)` guard, and `ulen` is bounded by netlink attribute size constraints. The integer truncation findings (#7-9) are also false positives because all the return values are native `int` types.
Finding #1 — Category B — INTEGER OVERFLOW — false positive
| Category | Cat B — integer overflow: sizeof(*rule) + size + usize |
|---|---|
| Taint source | be64_to_cpu() line 4364 |
| Taint snippet | handle = be64_to_cpu(nla_get_be64(nla[NFTA_RULE_HANDLE])); |
| Tainted var | rule |
| Overflow expr | sizeof(*rule) + size + usize |
| Safe fix | kmalloc_array() or check_mul_overflow() |
| Sink | kzalloc() line 4438 (arg 0, role=size_mul_overflow) |
| Sink snippet | rule = kzalloc(sizeof(*rule) + size + usize, GFP_KERNEL_ACCOUNT); |
| Possibly guarded | no |
Dismissed: The scanner incorrectly traces taint from `handle` (a u64 handle) into `rule` via `__nft_rule_lookup`, then into the kzalloc size expression. In reality, `size` is bounded by `if (size >= 1 << 12) goto err_release_expr` (< 4096 bytes), and `usize` is bounded by netlink attribute limits. `sizeof(*rule)` is a compile-time constant. No overflow is possible. The allocation `sizeof(*rule) + size + usize` is safe: max is ~(sizeof(nft_rule) + 4095 + sizeof(nft_userdata) + 65535) which fits in size_t with no wraparound.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be64_to_cpu() line 4364 |
| Taint snippet | handle = be64_to_cpu(nla_get_be64(nla[NFTA_RULE_HANDLE])); |
| Tainted var | rule |
| Pointer deref | rule->handle line 4444 |
| Sink snippet | rule->handle = handle; |
| Possibly guarded | yes (heuristic) |
Dismissed: The `rule` at line 4444 is a freshly kernel-allocated struct via `kzalloc()` at line 4438, not a pointer derived from a server-supplied offset. The scanner incorrectly propagates taint from the handle lookup. Writing `rule->handle = handle` is writing into a locally-allocated buffer - entirely safe.
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be64_to_cpu() line 4364 |
| Taint snippet | handle = be64_to_cpu(nla_get_be64(nla[NFTA_RULE_HANDLE])); |
| Tainted var | rule |
| Pointer deref | rule->dlen line 4445 |
| Sink snippet | rule->dlen = size; |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #2. `rule` is kernel-allocated. `rule->dlen = size` writes a locally-computed bounded value into a local struct. False positive.
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be64_to_cpu() line 4364 |
| Taint snippet | handle = be64_to_cpu(nla_get_be64(nla[NFTA_RULE_HANDLE])); |
| Tainted var | rule |
| Pointer deref | rule->udata line 4446 |
| Sink snippet | rule->udata = ulen ? 1 : 0; |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #2. `rule->udata = ulen ? 1 : 0` writes a boolean into a kernel-allocated struct. False positive.
Finding #5 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be64_to_cpu() line 4364 |
| Taint snippet | handle = be64_to_cpu(nla_get_be64(nla[NFTA_RULE_HANDLE])); |
| Tainted var | udata |
| Pointer deref | udata->len line 4450 |
| Sink snippet | udata->len = ulen - 1; |
| Possibly guarded | no |
Dismissed: `udata = nft_userdata(rule)` derives `udata` from the kernel-allocated `rule` pointer, which was sized to include `usize = sizeof(struct nft_userdata) + ulen`. So `udata->len` access is within the allocated buffer. The taint chain from `be64_to_cpu` to `udata` is a scanner artifact. False positive.
Finding #6 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be64_to_cpu() line 4364 |
| Taint snippet | handle = be64_to_cpu(nla_get_be64(nla[NFTA_RULE_HANDLE])); |
| Tainted var | udata |
| Pointer deref | udata->data line 4451 |
| Sink snippet | nla_memcpy(udata->data, nla[NFTA_RULE_USERDATA], ulen); |
| Possibly guarded | no |
Dismissed: Same as finding #5. `udata->data` is within the kernel-allocated buffer sized to hold it. `nla_memcpy` copies exactly `ulen` bytes which is the size allocated. False positive.
Finding #7 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | be64_to_cpu() line 4456 |
| Taint snippet | err = nf_tables_newexpr(&ctx, &expr_info[i], expr); |
| Tainted var | err |
| Truncation | line 4456: 64 → 32-bit u32 |
| Sink snippet | err = nf_tables_newexpr(&ctx, &expr_info[i], expr); |
| Possibly guarded | no |
Dismissed: `nf_tables_newexpr()` returns `int` (32-bit), and `err` is declared as `int`. There is no 64-bit to 32-bit truncation. The scanner incorrectly believes this is a wide-to-narrow truncation because it propagated taint from the 64-bit `handle` variable through multiple function calls. False positive.
Finding #8 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | be64_to_cpu() line 4472 |
| Taint snippet | err = PTR_ERR(flow); |
| Tainted var | err |
| Truncation | line 4472: 64 → 32-bit u32 |
| Sink snippet | err = PTR_ERR(flow); |
| Possibly guarded | no |
Dismissed: `PTR_ERR(flow)` returns `long` which is assigned to `int err`. On 64-bit systems, PTR_ERR values are small negative errno values that fit in int. This is a standard kernel pattern. Even if technically a narrowing, it's safe because error pointers encode small errno values. The taint propagation from `be64_to_cpu` to `flow` to `err` is incorrect. False positive.
Finding #9 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | be64_to_cpu() line 4488 |
| Taint snippet | err = nft_delrule(&ctx, old_rule); |
| Tainted var | err |
| Truncation | line 4488: 64 → 32-bit u32 |
| Sink snippet | err = nft_delrule(&ctx, old_rule); |
| Possibly guarded | no |
Dismissed: `nft_delrule()` returns `int`. `err` is `int`. No truncation occurs. The scanner's taint propagation from `be64_to_cpu(handle)` through `old_rule` to `nft_delrule` to `err` is a false chain. False positive.
Finding #10 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be64_to_cpu() line 4364 |
| Taint snippet | handle = be64_to_cpu(nla_get_be64(nla[NFTA_RULE_HANDLE])); |
| Tainted var | rule |
| Pointer deref | rule->list line 4497 |
| Sink snippet | list_add_tail_rcu(&rule->list, &old_rule->list); |
| Possibly guarded | yes (heuristic) |
Dismissed: `rule` at line 4497 is the freshly kernel-allocated struct. `list_add_tail_rcu(&rule->list, ...)` accesses the kernel-allocated buffer. False positive from incorrect taint propagation.
Finding #11 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be64_to_cpu() line 4364 |
| Taint snippet | handle = be64_to_cpu(nla_get_be64(nla[NFTA_RULE_HANDLE])); |
| Tainted var | rule |
| Pointer deref | rule->list line 4507 |
| Sink snippet | list_add_rcu(&rule->list, &old_rule->list); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #10. `rule->list` at line 4507 is within the kernel-allocated buffer. False positive.
Finding #12 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be64_to_cpu() line 4364 |
| Taint snippet | handle = be64_to_cpu(nla_get_be64(nla[NFTA_RULE_HANDLE])); |
| Tainted var | rule |
| Pointer deref | rule->list line 4509 |
| Sink snippet | list_add_tail_rcu(&rule->list, &chain->rules); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #10. `rule->list` at line 4509 is within the kernel-allocated buffer. False positive.
Finding #13 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be64_to_cpu() line 4364 |
| Taint snippet | handle = be64_to_cpu(nla_get_be64(nla[NFTA_RULE_HANDLE])); |
| Tainted var | rule |
| Pointer deref | rule->list line 4512 |
| Sink snippet | list_add_tail_rcu(&rule->list, &old_rule->list); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #10. `rule->list` at line 4512 is within the kernel-allocated buffer. False positive.
Finding #14 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be64_to_cpu() line 4364 |
| Taint snippet | handle = be64_to_cpu(nla_get_be64(nla[NFTA_RULE_HANDLE])); |
| Tainted var | rule |
| Pointer deref | rule->list line 4514 |
| Sink snippet | list_add_rcu(&rule->list, &chain->rules); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #10. `rule->list` at line 4514 is within the kernel-allocated buffer. False positive.
nf_tables_newset() — net/netfilter/nf_tables_api.c FP confidence=high
All 20 findings are false positives. The taint source is `flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS]))` from a user-space netlink message (not a network server response), and `flags` is properly bounds-checked at lines 5474-5491 with a whitelist mask check and several invalid combination checks. The `set` pointer is NOT derived from `flags` via offset arithmetic — it is allocated by `kvzalloc(alloc_size, GFP_KERNEL_ACCOUNT)` at line 5655 and is a locally-allocated kernel struct. The scanner has incorrectly propagated taint from `flags` to `alloc_size` (via `size = ops->privsize(nla, &desc)` and `udlen = nla_len(...)`) and then to `set`. However: (1) `alloc_size` is independently guarded at line 5649 (`alloc_size < size || alloc_size > INT_MAX`); (2) `set` is allocated with `alloc_size` bytes and is a kernel-managed heap object; (3) all field accesses on `set` are writes to a freshly allocated, correctly-sized buffer — not reads from a received packet. The Category E findings (#2-#20) are especially clearly false positives because `set` is not a pointer derived from a server-supplied offset — it is a pointer returned by `kvzalloc`. The taint propagation from `flags` to `set` in the scanner is spurious. | The scanner incorrectly propagated taint from the user-supplied 'flags' value (ntohl at line 5473) through alloc_size computation to the 'set' pointer returned by kvzalloc(). The 'set' pointer is a fresh kernel heap allocation (kvzalloc at line 5655), not a pointer derived from server-supplied offset arithmetic. All struct field accesses on 'set' are accesses into a locally-allocated nft_set struct. The taint chain flags->size->alloc_size->set is not a security-relevant taint propagation: kvzalloc() takes a size argument but always returns a fresh allocation at a kernel-controlled address. Additionally, set->num_exprs is written locally (line 5709) from a locally-maintained counter, not from user input.
Finding #1 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | alloc_size |
| Sink | kvzalloc() line 5655 (arg 0, role=size) |
| Sink snippet | set = kvzalloc(alloc_size, GFP_KERNEL_ACCOUNT); |
| Possibly guarded | yes (heuristic) |
Dismissed: flags is user-supplied (netlink, not a server response). It is checked against a strict whitelist mask at line 5474 and several mutually-exclusive combination checks follow. alloc_size is computed as sizeof(*set) + size + udlen; size comes from ops->privsize() (a kernel function) and udlen from nla_len() (bounded by netlink attribute length). The overflow guard at line 5649 (alloc_size < size || alloc_size > INT_MAX) ensures no integer overflow or undersized allocation. No counterexample exists that passes all guards yet causes harm.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->data line 5674 |
| Sink snippet | udata = set->data + size; |
| Possibly guarded | yes (heuristic) |
Dismissed: set is not derived from a server-supplied offset. It is a pointer returned by kvzalloc(alloc_size) at line 5655 — a locally-allocated kernel struct of exactly alloc_size bytes. set->data is the flexible member/trailing data region within that allocation, accessed at offset size which was included in alloc_size. The scanner incorrectly propagated taint from flags to set.
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->bindings line 5678 |
| Sink snippet | INIT_LIST_HEAD(&set->bindings); |
| Possibly guarded | yes (heuristic) |
Dismissed: set is a locally kernel-allocated struct via kvzalloc. set->bindings is a field within that allocation — a simple write to a locally-created object. Spurious taint propagation from flags.
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->catchall_list line 5679 |
| Sink snippet | INIT_LIST_HEAD(&set->catchall_list); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as #3 — set is a locally-allocated kernel struct. INIT_LIST_HEAD on set->catchall_list is a safe write within the allocated buffer.
Finding #5 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->refs line 5680 |
| Sink snippet | refcount_set(&set->refs, 1); |
| Possibly guarded | yes (heuristic) |
Dismissed: set is locally allocated. refcount_set on set->refs is a safe field write.
Finding #6 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->table line 5681 |
| Sink snippet | set->table = table; |
| Possibly guarded | yes (heuristic) |
Dismissed: set is locally allocated. set->table write is safe.
Finding #7 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->net line 5682 |
| Sink snippet | write_pnet(&set->net, net); |
| Possibly guarded | yes (heuristic) |
Dismissed: set is locally allocated. write_pnet on set->net is safe.
Finding #8 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->ops line 5683 |
| Sink snippet | set->ops = ops; |
| Possibly guarded | yes (heuristic) |
Dismissed: set is locally allocated. set->ops write is safe.
Finding #9 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->ktype line 5684 |
| Sink snippet | set->ktype = desc.ktype; |
| Possibly guarded | yes (heuristic) |
Dismissed: set is locally allocated. set->ktype write is safe.
Finding #10 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->klen line 5685 |
| Sink snippet | set->klen = desc.klen; |
| Possibly guarded | yes (heuristic) |
Dismissed: set is locally allocated. set->klen write is safe.
Finding #11 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->dtype line 5686 |
| Sink snippet | set->dtype = desc.dtype; |
| Possibly guarded | yes (heuristic) |
Dismissed: set is locally allocated. set->dtype write is safe.
Finding #12 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->objtype line 5687 |
| Sink snippet | set->objtype = desc.objtype; |
| Possibly guarded | yes (heuristic) |
Dismissed: set is locally allocated. set->objtype write is safe.
Finding #13 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->dlen line 5688 |
| Sink snippet | set->dlen = desc.dlen; |
| Possibly guarded | yes (heuristic) |
Dismissed: set is locally allocated. set->dlen write is safe.
Finding #14 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->flags line 5689 |
| Sink snippet | set->flags = flags; |
| Possibly guarded | yes (heuristic) |
Dismissed: set is locally allocated. set->flags write is safe. flags itself was validated by the whitelist mask check at line 5474.
Finding #15 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->size line 5690 |
| Sink snippet | set->size = desc.size; |
| Possibly guarded | yes (heuristic) |
Dismissed: set is locally allocated. set->size write is safe.
Finding #16 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->policy line 5691 |
| Sink snippet | set->policy = desc.policy; |
| Possibly guarded | yes (heuristic) |
Dismissed: set is locally allocated. set->policy write is safe.
Finding #17 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->udlen line 5692 |
| Sink snippet | set->udlen = udlen; |
| Possibly guarded | yes (heuristic) |
Dismissed: set is locally allocated. set->udlen write is safe. udlen itself is bounded by nla_len() which is bounded by the netlink message size.
Finding #18 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->udata line 5693 |
| Sink snippet | set->udata = udata; |
| Possibly guarded | yes (heuristic) |
Dismissed: set is locally allocated. set->udata write is safe; udata points into the trailing region of the allocation.
Finding #19 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->timeout line 5694 |
| Sink snippet | set->timeout = desc.timeout; |
| Possibly guarded | yes (heuristic) |
Dismissed: set is locally allocated. set->timeout write is safe.
Finding #20 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->gc_int line 5695 |
| Sink snippet | set->gc_int = desc.gc_int; |
| Possibly guarded | yes (heuristic) |
Dismissed: set is locally allocated. set->gc_int write is safe.
Finding #21 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->field_count line 5697 |
| Sink snippet | set->field_count = desc.field_count; |
| Possibly guarded | yes (heuristic) |
Dismissed: set is allocated by kvzalloc() at line 5655; it is a kernel-local allocation, not a pointer derived from server-supplied offset arithmetic. The taint propagation flags->set->set->field_count is a scanner artifact.
Finding #22 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->field_len line 5699 |
| Sink snippet | set->field_len[i] = desc.field_len[i]; |
| Possibly guarded | yes (heuristic) |
Dismissed: set->field_len[] is an array within the kernel-allocated nft_set struct. desc.field_count is bounded by NFT_REG32_COUNT in practice. set is a local kernel allocation.
Finding #23 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->exprs line 5705 |
| Sink snippet | err = nft_set_expr_alloc(&ctx, set, nla, set->exprs, &num_exprs, flags); |
| Possibly guarded | yes (heuristic) |
Dismissed: set->exprs is a field of the locally-allocated nft_set struct. The scanner incorrectly traces taint from flags to set.
Finding #24 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->num_exprs line 5709 |
| Sink snippet | set->num_exprs = num_exprs; |
| Possibly guarded | yes (heuristic) |
Dismissed: set->num_exprs is written with a locally-maintained counter num_exprs returned from nft_set_expr_alloc(). The set pointer is a local kernel allocation.
Finding #25 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->handle line 5710 |
| Sink snippet | set->handle = nf_tables_alloc_handle(table); |
| Possibly guarded | yes (heuristic) |
Dismissed: set->handle is assigned from nf_tables_alloc_handle(table) which increments a local counter. set is a kernel allocation.
Finding #26 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->pending_update line 5711 |
| Sink snippet | INIT_LIST_HEAD(&set->pending_update); |
| Possibly guarded | yes (heuristic) |
Dismissed: INIT_LIST_HEAD on a field of a locally-allocated struct. No server-supplied offset involved.
Finding #27 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->list line 5717 |
| Sink snippet | list_add_tail_rcu(&set->list, &table->sets); |
| Possibly guarded | yes (heuristic) |
Dismissed: list_add_tail_rcu on a field of a locally-allocated struct. The set pointer is not derived from any server offset.
Finding #28 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Loop | for_loop line 5722 |
| Sink snippet | for (i = 0; i < set->num_exprs; i++) |
| Possibly guarded | yes (heuristic) |
Dismissed: set->num_exprs was set at line 5709 to num_exprs, a locally-maintained counter from nft_set_expr_alloc(). The error-path loop at line 5722 iterates exactly over the expressions that were successfully allocated. No OOB possible.
Finding #29 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->num_exprs line 5722 |
| Sink snippet | for (i = 0; i < set->num_exprs; i++) |
| Possibly guarded | yes (heuristic) |
Dismissed: set->num_exprs is a field of a locally-allocated struct written with a locally-maintained counter. False positive.
Finding #30 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->exprs line 5723 |
| Sink snippet | nft_expr_destroy(&ctx, set->exprs[i]); |
| Possibly guarded | yes (heuristic) |
Dismissed: set->exprs is a field of the locally-allocated nft_set struct. The error-path loop is bounded by num_exprs which was locally counted.
Finding #31 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 5473 |
| Taint snippet | flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS])); |
| Tainted var | set |
| Pointer deref | set->name line 5727 |
| Sink snippet | kfree(set->name); |
| Possibly guarded | yes (heuristic) |
Dismissed: set->name is a field of the locally-allocated nft_set struct. kfree on a kernel-controlled pointer in the error path. No server-supplied offset involved.
nfnl_cthelper_parse_expect_policy() — net/netfilter/nfnetlink_cthelper.c FP confidence=high
The function validates class_max with both a lower bound (> 0) and upper bound (<= NF_CT_MAX_EXPECT_CLASSES) check before the loop. Both array accesses in the loop (tb[] and helper->expect_policy[]) are bounded by NF_CT_MAX_EXPECT_CLASSES, which is the enforced maximum. No counterexample can be constructed that passes the guard yet causes OOB.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 199 |
| Taint snippet | class_max = ntohl(nla_get_be32(tb[NFCTH_POLICY_SET_NUM])); |
| Tainted var | class_max |
| Loop | for_loop line 205 |
| Sink snippet | for (i = 0; i < class_max; i++) { |
| Possibly guarded | yes (heuristic) |
Dismissed: class_max is validated: zero check (line 200-201) and upper-bound check against NF_CT_MAX_EXPECT_CLASSES (line 202-203). The loop index i < class_max <= NF_CT_MAX_EXPECT_CLASSES, so helper->expect_policy[i] stays within the array bounds. The tb[] access with NFCTH_POLICY_SET+i is safe because NFCTH_POLICY_SET_MAX is defined to accommodate all NF_CT_MAX_EXPECT_CLASSES policy slots. No counterexample could be constructed that bypasses these guards.
nfnl_hook_dump_start() — net/netfilter/nfnetlink_hook.c FP confidence=high
nfnl_hook_entries_head() validates the tainted 'hook' parameter against ARRAY_SIZE() for each array before accessing it. Every flagged array subscript is immediately preceded by a bounds check that returns ERR_PTR on failure. The function is effectively the validator for the hook number. The caller also has a preliminary sanity check (hooknum > 255), but the per-array ARRAY_SIZE guards in the callee are the definitive protection.
Finding #1 — Category C — cross-function via nfnl_hook_entries_head() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohl() line 392 |
| Taint snippet | hooknum = ntohl(nla_get_be32(nla[NFNLA_HOOK_HOOKNUM])); |
| Tainted var | hooknum |
| Call site | line 405 — passes hooknum to nfnl_hook_entries_head() |
| Call snippet | head = nfnl_hook_entries_head(family, hooknum, net, name); |
| Subscript (in callee) | [] line 290 |
| Sink snippet | hook_head = rcu_dereference(net->nf.hooks_ipv4[hook]); |
| Possibly guarded | yes (heuristic) |
Dismissed: Line 288 checks `hook >= ARRAY_SIZE(net->nf.hooks_ipv4)` and returns ERR_PTR(-EINVAL) before line 290 accesses hooks_ipv4[hook]. No counterexample exists: any hook value that reaches line 290 satisfies hook < ARRAY_SIZE(hooks_ipv4).
Finding #2 — Category C — cross-function via nfnl_hook_entries_head() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohl() line 392 |
| Taint snippet | hooknum = ntohl(nla_get_be32(nla[NFNLA_HOOK_HOOKNUM])); |
| Tainted var | hooknum |
| Call site | line 405 — passes hooknum to nfnl_hook_entries_head() |
| Call snippet | head = nfnl_hook_entries_head(family, hooknum, net, name); |
| Subscript (in callee) | [] line 295 |
| Sink snippet | hook_head = rcu_dereference(net->nf.hooks_ipv6[hook]); |
| Possibly guarded | yes (heuristic) |
Dismissed: Line 293 checks `hook >= ARRAY_SIZE(net->nf.hooks_ipv6)` and returns ERR_PTR(-EINVAL) before line 295 accesses hooks_ipv6[hook]. No counterexample exists.
Finding #3 — Category C — cross-function via nfnl_hook_entries_head() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohl() line 392 |
| Taint snippet | hooknum = ntohl(nla_get_be32(nla[NFNLA_HOOK_HOOKNUM])); |
| Tainted var | hooknum |
| Call site | line 405 — passes hooknum to nfnl_hook_entries_head() |
| Call snippet | head = nfnl_hook_entries_head(family, hooknum, net, name); |
| Subscript (in callee) | [] line 301 |
| Sink snippet | hook_head = rcu_dereference(net->nf.hooks_arp[hook]); |
| Possibly guarded | yes (heuristic) |
Dismissed: Line 299 checks `hook >= ARRAY_SIZE(net->nf.hooks_arp)` and returns ERR_PTR(-EINVAL) before line 301 accesses hooks_arp[hook]. No counterexample exists.
Finding #4 — Category C — cross-function via nfnl_hook_entries_head() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohl() line 392 |
| Taint snippet | hooknum = ntohl(nla_get_be32(nla[NFNLA_HOOK_HOOKNUM])); |
| Tainted var | hooknum |
| Call site | line 405 — passes hooknum to nfnl_hook_entries_head() |
| Call snippet | head = nfnl_hook_entries_head(family, hooknum, net, name); |
| Subscript (in callee) | [] line 308 |
| Sink snippet | hook_head = rcu_dereference(net->nf.hooks_bridge[hook]); |
| Possibly guarded | yes (heuristic) |
Dismissed: Line 307 checks `hook >= ARRAY_SIZE(net->nf.hooks_bridge)` and returns ERR_PTR(-EINVAL) before line 308 accesses hooks_bridge[hook]. No counterexample exists.
nft_cmp_select_ops() — net/netfilter/nft_cmp.c BUG confidence=medium
The function reads a user-supplied register identifier as u32 but stores it in u8, causing silent truncation. The truncated value is used only for ops-path selection here; actual register validation happens in nft_cmp_init(). The truncation could cause wrong ops selection but is not a direct memory-safety issue.
Finding #1 — Category H — BUG integer_overflow
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohl() line 414 |
| Taint snippet | sreg = ntohl(nla_get_be32(tb[NFTA_CMP_SREG])); |
| Tainted var | sreg |
| Truncation | line 414: 32 → 8-bit u8 |
| Sink snippet | sreg = ntohl(nla_get_be32(tb[NFTA_CMP_SREG])); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: Wrong expression ops (nft_cmp16_fast_ops) selected for a register that is not actually a valid aligned NFT_REG32; could cause incorrect nftables rule evaluation or a mismatch between select_ops and init behavior leading to unexpected kernel behavior
Fix: Declare 'sreg' as u32 (not u8) to preserve the full 32-bit value before performing register range checks, e.g.: u32 sreg = ntohl(nla_get_be32(tb[NFTA_CMP_SREG])); This prevents a value like 0x108 from truncating to 0x08 and falsely matching a valid register.
nft_reg_to_type() — net/netfilter/nft_immediate.c BUG confidence=medium
The value from nla_get_be32() is genuinely user-supplied netlink data. The 32-bit ntohl() result is silently truncated to u8 before the NFT_REG_VERDICT comparison. This can cause register 256 (0x100) to be misidentified as NFT_REG_VERDICT (0), leading to wrong data type selection (NFT_DATA_VERDICT instead of NFT_DATA_VALUE). The impact is a logic error rather than direct memory corruption, since reg is only used for the equality comparison and not as an array index in this function.
Finding #1 — Category H — BUG integer_overflow
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohl() line 37 |
| Taint snippet | reg = ntohl(nla_get_be32(nla)); |
| Tainted var | reg |
| Truncation | line 37: 32 → 8-bit u8 |
| Sink snippet | reg = ntohl(nla_get_be32(nla)); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: A userspace process specifying register number 256 (0x100) would have it silently truncated to 0, misidentifying it as NFT_REG_VERDICT; subsequent nft_data_init() called with wrong type may fail unexpectedly or process data as verdict type incorrectly, potentially causing unexpected rule behavior or -EINVAL from type mismatch in later validation.
Fix: Use u32 (not u8) for reg: 'u32 reg = ntohl(nla_get_be32(nla));' to preserve the full 32-bit value before comparison. Alternatively, compare the raw be32 value directly against cpu_to_be32(NFT_REG_VERDICT) without converting to host byte order and truncating.
xt_data_to_user() — net/netfilter/x_tables.c FP confidence=high
The function xt_data_to_user() is a kernel-internal helper that copies xtables match/target data to userspace. The 'usersize' and 'size' parameters are passed in by kernel code (from struct xt_match/xt_target fields like .usersize and .matchsize/.targetsize), which are statically initialized by kernel modules — not read from userspace or from network packets. The taint source identified by the scanner (copy_to_user itself) is a false alarm; the scanner appears to be treating the destination of copy_to_user as a taint source, which is not meaningful here. The 'usersize' value comes from kernel-internal struct fields set at module load time by trusted kernel code, not from user-supplied data. The function also ensures aligned_size >= usersize semantics (the caller is responsible for passing consistent values). This is a well-established kernel utility function with no user-controlled size issue.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 319 |
| Taint snippet | if (copy_to_user(dst, src, usersize)) |
| Tainted var | usersize |
| Unvalidated size | copy_to_user() arg 2 line 319 — size usersize |
| Sink snippet | if (copy_to_user(dst, src, usersize)) |
| Possibly guarded | no |
Dismissed: The scanner misidentifies 'usersize' as tainted, apparently by treating copy_to_user() itself as a taint source. In reality, 'usersize' is a kernel-internal value derived from struct xt_match/xt_target fields (e.g., .usersize, .matchsize), which are statically initialized by kernel modules at compile/load time — not supplied by users or network peers. The callers of xt_data_to_user() are kernel functions like xt_match_to_user() and xt_target_to_user() that pass these kernel-internal constants. No counterexample can be constructed because the values are not attacker-controlled. This is a false positive.
xt_obj_to_user() — net/netfilter/x_tables.c FP confidence=high
The function xt_obj_to_user() copies kernel-internal data TO userspace. The 'name' parameter is a kernel-internal string (e.g., a match/target module name like 'tcp', 'ACCEPT') whose length is determined by strlen() on a kernel-owned buffer. The copy_to_user destination 'pname' is a userspace buffer. The size argument strlen(name)+1 is NOT user-supplied or server-supplied — it is computed from a kernel-internal string. The scanner incorrectly flagged this because copy_to_user() itself is a taint source in its model, but the size here comes from the kernel's own 'name' string, not from user input. The real concern would be whether pname's userspace buffer is large enough to hold the name, but that is the caller's responsibility and the name strings in xt_tables are module names bounded by XT_EXTENSION_MAXNAMELEN (29 bytes), making overflow of any properly-sized userspace field practically impossible.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 302 |
| Taint snippet | if (copy_to_user(pname, name, strlen(name) + 1)) |
| Tainted var | strlen(name) + 1 |
| Unvalidated size | copy_to_user() arg 2 line 302 — size strlen(name) + 1 |
| Sink snippet | if (copy_to_user(pname, name, strlen(name) + 1)) |
| Possibly guarded | no |
Dismissed: The 'name' parameter is a kernel-internal string (match/target name) stored in kernel memory, not user-supplied or server-supplied data. strlen(name)+1 is computed from a kernel-owned buffer whose content is controlled by the kernel (populated from registered xt_match/xt_target structures with names bounded by XT_EXTENSION_MAXNAMELEN). The scanner treats copy_to_user() as a taint source and then flags the same copy_to_user() as a sink, which is a circular false positive. No counterexample is constructible because the name strings originate from kernel module registration, not external input.
netlbl_af4list_audit_addr() — net/netlabel/netlabel_addrlist.c FP confidence=high
The flagged loop is a pure bit-counting operation on a u32 local variable with no memory access whatsoever. The loop shifts mask_val left and counts iterations; since mask_val is a 32-bit unsigned integer, the loop terminates in at most 32 iterations regardless of input. There is no buffer, no array, no pointer dereference — the scanner has incorrectly classified an arithmetic loop as a buffer traversal.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 314 |
| Taint snippet | u32 mask_val = ntohl(mask); |
| Tainted var | mask_val |
| Loop | while_loop line 322 |
| Sink snippet | while (mask_val > 0) { |
| Possibly guarded | yes (heuristic) |
Dismissed: The loop performs bitwise left-shifts on a u32 local variable (mask_val) and counts how many shifts are needed to drain all bits. After at most 32 iterations the u32 becomes 0 and the loop exits. No buffer, pointer, or array is involved — there is no possible OOB access. No counterexample can be constructed because the loop bound is structurally limited to 32 by the width of u32. The taint analysis tool incorrectly categorized this arithmetic bit-counting loop as a buffer-bounds problem. False positive.
netlbl_af6list_audit_addr() — net/netlabel/netlabel_addrlist.c FP confidence=high
The flagged loop is a bit-counting loop (popcount-style) that left-shifts a u32 until it becomes zero. It performs no array/buffer access inside the loop body, and terminates in at most 32 iterations by the nature of finite-width integer arithmetic. The scanner incorrectly categorized this as a buffer-iteration loop controlled by an unbounded server-supplied count.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 360 |
| Taint snippet | mask_val = ntohl(mask->s6_addr32[iter]); |
| Tainted var | mask_val |
| Loop | while_loop line 361 |
| Sink snippet | while (mask_val > 0) { |
| Possibly guarded | no |
Dismissed: The loop 'while (mask_val > 0) { mask_val <<= 1; mask_len++; }' is a bit-counting loop, not a buffer traversal. It accesses no array elements — it only shifts a u32 register variable left on each iteration. Since mask_val is a 32-bit unsigned integer, the loop terminates in at most 32 iterations regardless of the input value: any u32 left-shifted 32 times becomes 0. No counterexample can be constructed because the loop body contains no memory access. mask_len accumulates at most 128 total (32 from the first while + 32 from this loop + up to 64 from s6_addr32 traversal), well within u32 range. This is a false positive — the static scanner confused a bit-width-bounded arithmetic loop for an unbounded buffer iteration loop.
qrtr_ns_worker() — net/qrtr/ns.c MIXED confidence=high
The packet is received from the network via kernel_recvmsg, so pkt->cmd is genuinely server-supplied. Finding #1 (line 674 subscript) lacks a bounds check — the check `cmd < ARRAY_SIZE(qrtr_ctrl_pkt_strings)` appears on line 673 as the condition of the if-statement, but the subscript on line 674 IS inside that condition (short-circuit evaluation means line 674 is only reached when the check passes). However, finding #2 (line 675) is also inside the same if-block and is therefore also guarded. Both uses of `cmd` as an array subscript are guarded by `cmd < ARRAY_SIZE(qrtr_ctrl_pkt_strings)` on line 673, which is the correct and sufficient bounds check. The scanner incorrectly marked finding #1 as 'Possibly guarded: no' — the check is present and tight.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le32_to_cpu() line 672 |
| Taint snippet | cmd = le32_to_cpu(pkt->cmd); |
| Tainted var | cmd |
| Subscript | [] line 674 |
| Sink snippet | qrtr_ctrl_pkt_strings[cmd]) |
| Possibly guarded | no |
Dismissed: The scanner marked this 'Possibly guarded: no', but looking at lines 673-676 carefully: the condition on line 673 is `cmd < ARRAY_SIZE(qrtr_ctrl_pkt_strings) && qrtr_ctrl_pkt_strings[cmd]`. The subscript on line 674 (`qrtr_ctrl_pkt_strings[cmd]`) is the second operand of the short-circuit &&, evaluated only when `cmd < ARRAY_SIZE(qrtr_ctrl_pkt_strings)` is true. Therefore the bounds check IS present and sufficient. No counterexample exists: any value of cmd >= ARRAY_SIZE would cause the first condition to be false, preventing evaluation of the subscript.
Finding #2 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | le32_to_cpu() line 672 |
| Taint snippet | cmd = le32_to_cpu(pkt->cmd); |
| Tainted var | cmd |
| Subscript | [] line 675 |
| Sink snippet | trace_qrtr_ns_message(qrtr_ctrl_pkt_strings[cmd], |
| Possibly guarded | yes (heuristic) |
Dismissed: The use on line 675 (`qrtr_ctrl_pkt_strings[cmd]` as argument to trace_qrtr_ns_message) is inside the body of the if-statement whose condition already verified `cmd < ARRAY_SIZE(qrtr_ctrl_pkt_strings) && qrtr_ctrl_pkt_strings[cmd]`. Both the range bound and the null-pointer check are satisfied before this line executes. No counterexample exists.
rds_add_bound() — net/rds/bind.c FP confidence=high
The function validates the port value against reserved values (0, RDS_FLAG_PROBE_PORT) before use. The flagged sink in __rds_create_bind_key() uses sizeof(port) as the memcpy size argument, which is sizeof(__be16) == 2 — a compile-time constant entirely independent of the tainted port value. The scanner incorrectly treated the tainted 'port' parameter as influencing sizeof(port).
Finding #1 — Category B — cross-function via __rds_create_bind_key() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | be16_to_cpu() line 102 |
| Taint snippet | rover = be16_to_cpu(*port); |
| Tainted var | rover |
| Call site | line 117 — passes rover to __rds_create_bind_key() |
| Call snippet | __rds_create_bind_key(key, addr, cpu_to_be16(rover), |
| Sink (in callee) | memcpy() line 61 (arg 2, role=size) |
| Sink snippet | memcpy(key, &port, sizeof(port)); |
| Possibly guarded | yes (heuristic) |
Dismissed: The memcpy size argument in __rds_create_bind_key() is sizeof(port) where port is of type __be16 — this evaluates to the compile-time constant 2, not the runtime value of port/rover. The taint cannot affect the size argument. Additionally, *port comes from user-space sockaddr (sin->sin_port), not a server/network response, making this doubly a false positive. No counterexample exists because sizeof() never uses the runtime value of its argument.
rds_cong_clear_bit() — net/rds/cong.c BUG confidence=high
The function rds_cong_clear_bit() accepts a __be16 port value and converts it to a CPU-native value via be16_to_cpu(). The port is a network-supplied 16-bit value (0–65535). It is divided by RDS_CONG_MAP_PAGE_BITS (which equals PAGE_SIZE*8 = 32768) to compute index i. The maximum port value 65535 gives i = 65535/32768 = 1 (integer division), which equals 1. RDS_CONG_MAP_PAGES is defined as 2 (since the congestion map covers 65536 bits = 2 pages). So the array m_page_addrs[] has exactly 2 entries (indices 0 and 1). The maximum i is 1, which is within bounds. However, there is no explicit bounds check in the code, and the safety relies entirely on the implicit constraint that be16_to_cpu() returns at most 65535, making i at most 1. If the array size RDS_CONG_MAP_PAGES ever changes, or if the value were not strictly 16-bit, an OOB could occur. Additionally, port=0 is technically valid and gives i=0. The implicit arithmetic safety is real but fragile and undocumented.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | be16_to_cpu() line 321 |
| Taint snippet | i = be16_to_cpu(port) / RDS_CONG_MAP_PAGE_BITS; |
| Tainted var | i |
| Subscript | [] line 324 |
| Sink snippet | clear_bit_le(off, (void *)map->m_page_addrs[i]); |
| Possibly guarded | no |
Dismissed: Counterexample attempt: be16_to_cpu(port) has maximum value 65535. With RDS_CONG_MAP_PAGE_BITS = PAGE_SIZE*8 = 32768, i = 65535/32768 = 1. RDS_CONG_MAP_PAGES = 2, so m_page_addrs[] has indices 0 and 1. No counterexample exists — i is always 0 or 1, both in bounds. The finding is technically a false positive from a strict safety standpoint, but the implicit reliance on arithmetic constraints without any explicit guard is a latent fragility issue. Marking real_bug=false and impact=none as no OOB is actually possible with current constants.
rds_cong_set_bit() — net/rds/cong.c BUG confidence=high
The function rds_cong_set_bit() takes a __be16 port value, converts it to host byte order, and uses the result divided by RDS_CONG_MAP_PAGE_BITS as an array index into map->m_page_addrs[]. No bounds check is performed on 'i' before the array access. A port value is a 16-bit quantity (0–65535), and RDS_CONG_MAP_PAGE_BITS is typically PAGE_SIZE*8 (32768), yielding i values of 0 or 1 for well-formed ports. However, RDS_CONG_MAP_PAGES defines the size of m_page_addrs[], and if a caller supplies a crafted or unexpected port value, or if the constants differ from expectations, an out-of-bounds array access is possible. The port parameter comes from network packets in the RDS receive path, making this genuinely server/peer-supplied.
Finding #1 — Category C — BUG oob_write
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | be16_to_cpu() line 307 |
| Taint snippet | i = be16_to_cpu(port) / RDS_CONG_MAP_PAGE_BITS; |
| Tainted var | i |
| Subscript | [] line 310 |
| Sink snippet | set_bit_le(off, (void *)map->m_page_addrs[i]); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds or wild pointer dereference in rds_cong_set_bit() when a peer sends a crafted or unexpected port value causing 'i' to exceed the bounds of map->m_page_addrs[]
Fix: Add a bounds check before the array access: if (i >= RDS_CONG_MAP_PAGES) { WARN_ON_ONCE(1); return; } — where RDS_CONG_MAP_PAGES is the declared size of m_page_addrs[]. This ensures 'i' is within valid array bounds before use.
CVE pattern: Array index out-of-bounds via network-supplied 16-bit value without range validation
rds_cong_test_bit() — net/rds/cong.c FP confidence=high
The port is a __be16 value, so be16_to_cpu() is mathematically bounded to [0, 65535]. With RDS_CONG_MAP_PAGE_BITS = PAGE_SIZE*8 = 32768 bits/page, the index i = port_val / 32768 can only be 0 or 1. The m_page_addrs array is sized as RDS_CONG_MAP_PAGES = DIV_ROUND_UP(65536/8, PAGE_SIZE) = 2, exactly covering all possible port values. No out-of-bounds access is possible by construction.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | be16_to_cpu() line 332 |
| Taint snippet | i = be16_to_cpu(port) / RDS_CONG_MAP_PAGE_BITS; |
| Tainted var | i |
| Subscript | [] line 335 |
| Sink snippet | return test_bit_le(off, (void *)map->m_page_addrs[i]); |
| Possibly guarded | no |
Dismissed: Although no explicit bounds check is present, the type constraint of __be16 limits be16_to_cpu(port) to [0, 65535]. Dividing by RDS_CONG_MAP_PAGE_BITS (32768) yields at most index 1. The m_page_addrs array has exactly 2 entries (RDS_CONG_MAP_PAGES=2), so i is always in [0,1]. No counterexample can be constructed — the arithmetic is safe by design matching the full __be16 port space.
rds_ib_inc_copy_to_user() — net/rds/ib_recv.c BUG confidence=medium
The function uses a peer-supplied h_len field to drive fragment list traversal without validating it against the actual number of fragments/bytes received. If h_len exceeds actual received data, the list_entry() call will walk past the end of the fragment list, producing an invalid pointer.
Finding #1 — Category F — BUG oob_read
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | be32_to_cpu() line 546 |
| Taint snippet | len = be32_to_cpu(inc->i_hdr.h_len); |
| Tainted var | len |
| Loop | while_loop line 548 |
| Sink snippet | while (iov_iter_count(to) && copied < len) { |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: General protection fault or KASAN use-after-free report in rds_ib_inc_copy_to_user when list_entry() dereferences past the end of ii_frags list; potential kernel panic or info disclosure from invalid page fragment pointer
Fix: Validate h_len against the actual number of bytes present in ii_frags (sum of fragment sizes or count*RDS_FRAG_SIZE) before entering the loop. Add a check such as: if (len > actual_frag_bytes) return -EINVAL; Alternatively, check list boundary (if frag->f_item.next == &ibinc->ii_frags) before advancing the fragment pointer.
CVE pattern: Peer-supplied length field driving linked-list traversal without bounds — similar to various network driver OOB patterns where received length fields are not validated against actual buffer/fragment counts
rds_ib_xmit() — net/rds/ib_send.c FP confidence=high
The server-supplied h_len value (tainted i) is passed to rds_ib_ring_alloc(), which caps the allocation at min(requested, available_slots) where available_slots = ring->w_nr - used. The returned work_alloc is therefore bounded by the ring size. The loop at line 652 iterates work_alloc times and accesses ic->i_sends[pos], where i_sends is sized to the ring capacity (w_nr). The ring's modular arithmetic also keeps pos within bounds. No OOB access is possible regardless of the h_len value.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | be32_to_cpu() line 524 |
| Taint snippet | i = DIV_ROUND_UP(be32_to_cpu(rm->m_inc.i_hdr.h_len), RDS_FRAG_SIZE); |
| Tainted var | i |
| Loop | do_loop line 652 |
| Sink snippet | do { |
| Possibly guarded | no |
Dismissed: Although h_len is genuinely network-supplied, rds_ib_ring_alloc() acts as a truncating gate: it returns min(val, avail) where avail <= ring->w_nr. work_alloc is therefore bounded by the ring size, and ic->i_sends is allocated with ring->w_nr entries. No counterexample exists: any h_len value, however large, results in work_alloc bounded by ring->w_nr, keeping all i_sends[] accesses in bounds. The additional check at line 542 (work_alloc == 0 → return -ENOMEM) further validates the lower bound.
rds_message_inc_copy_to_user() — net/rds/message.c BUG confidence=medium
The function reads h_len from an incoming message header (network-supplied) and uses it to control a loop that advances a scatter-gather pointer without bounds-checking the sg array. If h_len exceeds the actual bytes in the sg list, sg++ can walk past the allocated array.
Finding #1 — Category F — BUG oob_read
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | be32_to_cpu() line 526 |
| Taint snippet | len = be32_to_cpu(rm->m_inc.i_hdr.h_len); |
| Tainted var | len |
| Loop | while_loop line 532 |
| Sink snippet | while (iov_iter_count(to) && copied < len) { |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in rds_message_inc_copy_to_user when a malicious peer sends a message with h_len larger than the actual scatter-gather data; could also cause kernel panic or info disclosure via sg_page() on out-of-bounds sg entry
Fix: Track the number of sg elements (rm->data.op_nents) and add a check before advancing sg: if the sg pointer would exceed rm->data.op_sg + rm->data.op_nents, break out of the loop or return -EMSGSIZE. Also validate len against the total bytes in the sg list before entering the loop.
CVE pattern: Missing scatter-gather array bounds check when iterating with network-supplied length
rfkill_fop_read() — net/rfkill/core.c FP confidence=high
The tainted variable 'sz' is not user-supplied or server-supplied — it is computed entirely within the kernel using min_t() on sizeof(ev->ev) (a compile-time constant), the user-supplied count argument (the destination buffer size the user declared), and data->max_size (a kernel-internal field). The copy_to_user() call is therefore safe: sz is bounded above by sizeof(ev->ev), which is the exact size of the source object being copied. The scanner appears to have flagged 'sz' because 'count' (a user-supplied argument) flows into it, but after two min_t() operations the value cannot exceed sizeof(ev->ev), making it impossible to read beyond the source struct or write beyond what the user declared as their buffer size.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 1272 |
| Taint snippet | if (copy_to_user(buf, &ev->ev, sz)) |
| Tainted var | sz |
| Unvalidated size | copy_to_user() arg 2 line 1272 — size sz |
| Sink snippet | if (copy_to_user(buf, &ev->ev, sz)) |
| Possibly guarded | no |
Dismissed: sz = min_t(unsigned long, sizeof(ev->ev), count) caps sz at sizeof(ev->ev) (a compile-time constant, the size of struct rfkill_event). The second min_t with data->max_size can only further reduce sz. Therefore sz <= sizeof(ev->ev) always holds before copy_to_user, preventing any over-read of the source buffer. The user-controlled value 'count' only influences sz downward. No counterexample exists where sz exceeds the source object size. False positive.
rxrpc_preparse_xdr() — net/rxrpc/key.c FP confidence=high
The function employs a rigorous two-phase approach: a full validation pass (lines 302-369) walks all data using pointer 'p' with per-step datalen checks and verifies datalen==0 at the end, then a processing pass (lines 375-412) re-traverses using 'xdr'. All loop bounds (len, paddedlen, ntoken) are validated against datalen before use. The callee functions (rxrpc_preparse_xdr_rxkad, rxrpc_preparse_xdr_yfs_rxgk) additionally validate their 'toklen' parameter internally before using derived values in allocations and memcpy calls. No genuine missing validation was found.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 320 |
| Taint snippet | len = ntohl(*xdr++); |
| Tainted var | len |
| Loop | for_loop line 329 |
| Sink snippet | for (loop = 0; loop < len; loop++) |
| Possibly guarded | yes (heuristic) |
Dismissed: len is bounded 1..AFSTOKEN_CELL_MAX at line 321. paddedlen=(len+3)&~3 is checked against datalen at line 325 (paddedlen>datalen → not_xdr). Since cp points into a buffer of size datalen and paddedlen≤datalen, loop indices 0..len-1 and len..paddedlen-1 are all within the buffer. No counterexample possible: any len that passes the guard satisfies paddedlen≤datalen, so cp[loop] is always in-bounds.
Finding #2 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 320 |
| Taint snippet | len = ntohl(*xdr++); |
| Tainted var | paddedlen |
| Loop | for_loop line 332 |
| Sink snippet | for (; loop < paddedlen; loop++) |
| Possibly guarded | yes (heuristic) |
Dismissed: paddedlen derives from len (server-supplied) but is checked against datalen at line 325 before the loop at line 332. The loop runs from len to paddedlen-1 inclusive, all within the validated buffer range. No counterexample exists because paddedlen≤datalen is enforced before reaching the loop.
Finding #3 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 343 |
| Taint snippet | ntoken = ntohl(*xdr++); |
| Tainted var | loop |
| Loop | do_loop line 352 |
| Sink snippet | do { |
| Possibly guarded | no |
Dismissed: loop=ntoken, bounded 1..AFSTOKEN_MAX at line 346. Inside the do-loop, per-iteration checks: datalen<8 guard (line 353), toklen<20||toklen>datalen||paddedlen>datalen guard (line 360). These per-iteration bounds checks terminate the loop before any OOB access, making the iteration count itself irrelevant to safety. No counterexample: any iteration that would exceed the buffer is caught by the inner datalen checks.
Finding #4 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 343 |
| Taint snippet | ntoken = ntohl(*xdr++); |
| Tainted var | ntoken |
| Loop | do_loop line 375 |
| Sink snippet | do { |
| Possibly guarded | yes (heuristic) |
Dismissed: The second do-loop (line 375) uses ntoken bounded 1..AFSTOKEN_MAX. More importantly, the first validation loop (lines 352-365) already walked all ntoken tokens using identical arithmetic (same base pointer region, same toklen/paddedlen per step) with full per-step bounds checks, and line 368 verifies datalen==0 (no leftover bytes). This constitutes a prior full-traversal validation that completely protects the second loop. False positive by protection pattern (c).
Finding #5 — Category B — cross-function via rxrpc_preparse_xdr_rxkad() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohl() line 376 |
| Taint snippet | toklen = ntohl(*xdr++); |
| Tainted var | toklen |
| Call site | line 387 — passes toklen to rxrpc_preparse_xdr_rxkad() |
| Call snippet | ret2 = rxrpc_preparse_xdr_rxkad(prep, datalen, token, toklen); |
| Sink (in callee) | kzalloc() line 82 (arg 0, role=size) |
| Sink snippet | token->kad = kzalloc(plen, GFP_KERNEL); |
| Possibly guarded | no |
Dismissed: toklen at line 376 was pre-validated by the first loop (toklen≥20 and fits within original buffer). Inside rxrpc_preparse_xdr_rxkad: checks toklen>8*4, extracts tktlen=ntohl(xdr[7]), validates tktlen≤AFSTOKEN_RK_TIX_MAX and toklen≥8*4+tktlen. kzalloc at line 82 uses plen=sizeof(*token)+sizeof(*token->kad)+tktlen which is bounded by AFSTOKEN_RK_TIX_MAX. Cannot construct counterexample that passes all guards yet causes oversized allocation.
Finding #6 — Category B — cross-function via rxrpc_preparse_xdr_rxkad() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohl() line 376 |
| Taint snippet | toklen = ntohl(*xdr++); |
| Tainted var | toklen |
| Call site | line 387 — passes toklen to rxrpc_preparse_xdr_rxkad() |
| Call snippet | ret2 = rxrpc_preparse_xdr_rxkad(prep, datalen, token, toklen); |
| Sink (in callee) | memcpy() line 96 (arg 2, role=size) |
| Sink snippet | memcpy(&token->kad->ticket, &xdr[8], tktlen); |
| Possibly guarded | yes (heuristic) |
Dismissed: memcpy at line 96 uses tktlen as size. tktlen is validated: tktlen≤AFSTOKEN_RK_TIX_MAX (line 69) and toklen≥8*4+tktlen (line 71). The destination buffer token->kad->ticket is allocated with tktlen bytes (plen includes tktlen). Source xdr[8] is valid because toklen≥8*4+tktlen ensures at least tktlen bytes after xdr[8]. No counterexample possible.
Finding #7 — Category B — cross-function via rxrpc_preparse_xdr_yfs_rxgk() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohl() line 376 |
| Taint snippet | toklen = ntohl(*xdr++); |
| Tainted var | toklen |
| Call site | line 390 — passes toklen to rxrpc_preparse_xdr_yfs_rxgk() |
| Call snippet | ret2 = rxrpc_preparse_xdr_yfs_rxgk(prep, datalen, token, toklen); |
| Sink (in callee) | kzalloc() line 213 (arg 0, role=size) |
| Sink snippet | token->rxgk = kzalloc(struct_size_t(struct rxgk_key, _key, raw_keylen), GFP_KERNEL); |
| Possibly guarded | yes (heuristic) |
Dismissed: toklen passed to rxrpc_preparse_xdr_yfs_rxgk is pre-validated by the first traversal loop. Inside the callee: raw_keylen bounded by AFSTOKEN_GK_KEY_MAX (line 186), keylen size checked against toklen (line 189), kzalloc at line 213 uses struct_size_t based on raw_keylen which is bounded. Cannot construct counterexample passing all guards.
Finding #8 — Category B — cross-function via rxrpc_preparse_xdr_yfs_rxgk() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohl() line 376 |
| Taint snippet | toklen = ntohl(*xdr++); |
| Tainted var | toklen |
| Call site | line 390 — passes toklen to rxrpc_preparse_xdr_yfs_rxgk() |
| Call snippet | ret2 = rxrpc_preparse_xdr_yfs_rxgk(prep, datalen, token, toklen); |
| Sink (in callee) | kzalloc() line 243 (arg 0, role=size) |
| Sink snippet | token->rxgk->ticket.data = kzalloc(tktlen, GFP_KERNEL); |
| Possibly guarded | yes (heuristic) |
Dismissed: kzalloc at line 243 uses tktlen=round_up(raw_tktlen,4). raw_tktlen bounded by AFSTOKEN_GK_TOKEN_MAX (line 195). Exact size equality check at line 198 ensures consistency. No counterexample possible: tktlen is bounded and verified against toklen.
Finding #9 — Category A — cross-function via rxrpc_preparse_xdr_yfs_rxgk() — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | ntohl() line 376 |
| Taint snippet | toklen = ntohl(*xdr++); |
| Tainted var | toklen |
| Call site | line 390 — passes toklen to rxrpc_preparse_xdr_yfs_rxgk() |
| Call snippet | ret2 = rxrpc_preparse_xdr_yfs_rxgk(prep, datalen, token, toklen); |
| Sink (in callee) | memcpy() line 246 (arg 1, role=pointer) |
| Sink snippet | memcpy(token->rxgk->ticket.data, ticket, token->rxgk->ticket.len); |
| Possibly guarded | no |
Dismissed: memcpy at line 246 copies ticket.len=raw_tktlen bytes from ticket pointer into allocated tktlen-byte buffer. raw_tktlen≤AFSTOKEN_GK_TOKEN_MAX, ticket pointer is computed as xdr+(6*2+1+keylen/4+1) and the exact size equality check (line 198) ensures ticket region plus key region equals toklen, so ticket is within the validated buffer. No counterexample possible.
rxrpc_preparse_xdr_rxkad() — net/rxrpc/key.c FP confidence=high
The function validates tktlen at lines 69-72 before any allocation or copy. Line 69 checks tktlen <= AFSTOKEN_RK_TIX_MAX (upper bound on value), and line 71-72 checks toklen >= 8*4 + tktlen (ensuring the source buffer is large enough). For finding #1 (kzalloc size): plen = sizeof(*token->kad) + tktlen, bounded by AFSTOKEN_RK_TIX_MAX. For finding #2 (memcpy): tktlen is bounded against the source buffer (toklen check) and the destination (token->kad->ticket is allocated as plen = sizeof(*token->kad)+tktlen, so ticket[] is tktlen bytes). Both checks are present and sufficient.
Finding #1 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohl() line 67 |
| Taint snippet | tktlen = ntohl(xdr[7]); |
| Tainted var | plen |
| Sink | kzalloc() line 82 (arg 0, role=size) |
| Sink snippet | token->kad = kzalloc(plen, GFP_KERNEL); |
| Possibly guarded | no |
Dismissed: tktlen is checked against AFSTOKEN_RK_TIX_MAX at line 69 before being used in plen. plen = sizeof(*token->kad) + tktlen, so it is structurally bounded. No counterexample can be constructed: any tktlen > AFSTOKEN_RK_TIX_MAX is rejected before the allocation.
Finding #2 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohl() line 67 |
| Taint snippet | tktlen = ntohl(xdr[7]); |
| Tainted var | tktlen |
| Sink | memcpy() line 96 (arg 2, role=size) |
| Sink snippet | memcpy(&token->kad->ticket, &xdr[8], tktlen); |
| Possibly guarded | yes (heuristic) |
Dismissed: Source buffer safety: line 71-72 ensures toklen >= 8*4 + tktlen, so xdr[8..8+tktlen-1] is within the passed-in xdr buffer. Destination safety: token->kad is allocated as sizeof(*token->kad)+tktlen bytes, and ticket[] is a flexible array at the end, so it holds exactly tktlen bytes. No counterexample can be constructed.
rxrpc_preparse_xdr_yfs_rxgk() — net/rxrpc/key.c FP confidence=high
The function has thorough validation: raw_keylen is capped by AFSTOKEN_GK_KEY_MAX before use in struct_size_t allocation; raw_tktlen is capped by AFSTOKEN_GK_TOKEN_MAX before use in kzalloc; an exact-equality check at line 198 ensures keylen+tktlen fits exactly within toklen; and the caller validates toklen fits within datalen. The ticket pointer arithmetic is covered by these combined constraints. No counterexample can be constructed that passes all guards yet causes OOB.
Finding #1 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohl() line 184 |
| Taint snippet | raw_keylen = ntohl(key[-1]); |
| Tainted var | raw_keylen |
| Sink | kzalloc() line 213 (arg 0, role=size) |
| Sink snippet | token->rxgk = kzalloc(struct_size_t(struct rxgk_key, _key, raw_keylen), GFP_KERNEL); |
| Possibly guarded | yes (heuristic) |
Dismissed: raw_keylen is checked against AFSTOKEN_GK_KEY_MAX at line 186 before being passed to struct_size_t(), which itself handles multiplication overflow. No counterexample: any raw_keylen > AFSTOKEN_GK_KEY_MAX is rejected before reaching the allocation.
Finding #2 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohl() line 193 |
| Taint snippet | raw_tktlen = ntohl(ticket[-1]); |
| Tainted var | tktlen |
| Sink | kzalloc() line 243 (arg 0, role=size) |
| Sink snippet | token->rxgk->ticket.data = kzalloc(tktlen, GFP_KERNEL); |
| Possibly guarded | yes (heuristic) |
Dismissed: raw_tktlen is checked against AFSTOKEN_GK_TOKEN_MAX at line 195. tktlen = round_up(raw_tktlen, 4) is safe given the constant upper bound. The exact-equality check at line 198 further validates the total. No counterexample can pass all guards while causing undersized allocation.
Finding #3 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | ntohl() line 184 |
| Taint snippet | raw_keylen = ntohl(key[-1]); |
| Tainted var | ticket |
| Sink | memcpy() line 246 (arg 1, role=pointer) |
| Sink snippet | memcpy(token->rxgk->ticket.data, ticket, token->rxgk->ticket.len); |
| Possibly guarded | no |
Dismissed: The ticket pointer is derived from xdr with offset 13 + keylen/4 + 1 words. The exact-equality check at line 198 guarantees (6*2+2)*4 + keylen + tktlen == toklen, and the caller ensures toklen <= datalen (the original buffer size). Since raw_tktlen <= tktlen and the ticket data region is exactly tktlen bytes within the buffer, the memcpy source read is in-bounds.
rxgk_verify_authenticator() — net/rxrpc/rxgk.c FP confidence=high
rxgk_verify_authenticator() has solid validation discipline throughout: it checks minimum buffer lengths, validates app_len against remaining buffer, and critically validates call_count both against the protocol maximum (>4 check) and against the remaining buffer space (end-p < call_count check) before the loop. The conn->channels[] array access is safe because call_count is bounded to ≤4 matching RXRPC_MAXCALLS.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 1119 |
| Taint snippet | call_count = ntohl(*p++); |
| Tainted var | call_count |
| Loop | for_loop line 1132 |
| Sink snippet | for (i = 0; i < call_count; i++) { |
| Possibly guarded | yes (heuristic) |
Dismissed: Two independent guards protect this loop: (1) line 1124 rejects call_count > 4, bounding it to [0,4] which matches RXRPC_MAXCALLS for safe conn->channels[i] indexing; (2) line 1128 checks end-p < call_count ensuring the buffer contains at least call_count __be32 words before the loop begins. No counterexample can be constructed: any call_count that passes both guards will result in a loop that stays within both the buffer bounds and the channels array bounds.
rxgk_verify_response() — net/rxrpc/rxgk.c FP confidence=high
rxgk_verify_response() has solid layered validation: it checks buffer lengths before extracting fields, validates auth_len against remaining buffer before passing to rxgk_verify_authenticator(). Inside rxgk_verify_authenticator(), the server-supplied call_count is validated both against a maximum of 4 (matching RXRPC's 4-channel limit) and against the remaining buffer size before being used as a loop bound. The scanner traced taint from auth_len through to the loop but did not recognize the intervening call_count > 4 guard and end-p < call_count guard that make the loop safe.
Finding #1 — Category F — cross-function via rxgk_verify_authenticator() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 1205 |
| Taint snippet | auth_len = ntohl(xauth_len); |
| Tainted var | auth_len |
| Call site | line 1254 — passes auth_len to rxgk_verify_authenticator() |
| Call snippet | ret = rxgk_verify_authenticator(conn, krb5, skb, auth, auth_len); |
| Loop | for_loop line 1132 |
| Sink snippet | for (i = 0; i < call_count; i++) { |
| Possibly guarded | yes (heuristic) |
Dismissed: The loop variable is not auth_len itself but call_count, which is separately validated at line 1124 (call_count > 4 aborts) and line 1128 (end - p < call_count aborts). No counterexample exists: call_count is bounded to [0,4] and buffer is confirmed to hold at least call_count __be32 entries before the loop. rxgk_verify_authenticator() is the validation function — accesses inside it are the validation logic, not vulnerable sinks.
rxgk_yfs_decode_ticket() — net/rxrpc/rxgk_app.c FP confidence=high
The function validates klen against ticket_len at line 66 before any memory operations. The guard `klen > ticket_len - 10*sizeof(__be32)` establishes klen <= ticket_len-40, ensuring both source reads (from ticket buffer) and destination writes (into the precisely-sized payload allocation) stay in bounds. The allocation size payload_len is structurally derived from the validated klen and ticket_len, and xdr_round_up only adds alignment padding. All five findings are false positives protected by this gate.
Finding #1 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohl() line 64 |
| Taint snippet | klen = ntohl(tmp[1]); |
| Tainted var | payload_len |
| Sink | kzalloc() line 75 (arg 0, role=size) |
| Sink snippet | payload = kzalloc(payload_len, GFP_NOFS); |
| Possibly guarded | no |
Dismissed: klen is bounded by the check at line 66: klen <= ticket_len - 40. payload_len = (19*4 + xdr_round_up(klen) + 4) + xdr_round_up(ticket_len). Since klen <= ticket_len <= UINT_MAX and ticket_len is a kernel-controlled unsigned int (from network buffer length), the allocation is properly sized. No counterexample can be constructed where the guard passes but the allocation is dangerously sized relative to subsequent use.
Finding #2 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | ntohl() line 64 |
| Taint snippet | klen = ntohl(tmp[1]); |
| Tainted var | ticket |
| Sink | memcpy() line 84 (arg 0, role=pointer) |
| Sink snippet | memcpy(ticket, buffer, ticket_len); |
| Possibly guarded | no |
Dismissed: ticket = payload + pre_ticket_len. The destination is the tail portion of the payload allocation, which was sized as pre_ticket_len + xdr_round_up(ticket_len). memcpy copies ticket_len bytes into this region — exactly fits. klen taint influences pre_ticket_len but payload is allocated large enough to hold both regions.
Finding #3 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohl() line 64 |
| Taint snippet | klen = ntohl(tmp[1]); |
| Tainted var | klen |
| Sink | memcpy() line 117 (arg 2, role=size) |
| Sink snippet | memcpy(q, ticket + sizeof(__be32) * 2, klen); |
| Possibly guarded | yes (heuristic) |
Dismissed: Source: ticket+8 to ticket+8+klen. Since klen <= ticket_len-40, and ticket has ticket_len bytes, reading klen bytes at offset 8 stays within ticket buffer (klen+8 <= ticket_len-32 < ticket_len). Destination: q is within pre_ticket_len region of payload, which was allocated with xdr_round_up(klen) bytes for exactly this copy. No counterexample possible.
Finding #4 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | ntohl() line 64 |
| Taint snippet | klen = ntohl(tmp[1]); |
| Tainted var | q |
| Sink | memcpy() line 117 (arg 0, role=pointer) |
| Sink snippet | memcpy(q, ticket + sizeof(__be32) * 2, klen); |
| Possibly guarded | no |
Dismissed: q = payload + 5*4 + 14*4 = payload + 76. pre_ticket_len = 19*4 + xdr_round_up(klen) + 4 >= 80 bytes, so q+xdr_round_up(klen) bytes is within the pre_ticket_len region. The destination pointer is well within the allocated payload buffer.
Finding #5 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | ntohl() line 64 |
| Taint snippet | klen = ntohl(tmp[1]); |
| Tainted var | ticket |
| Sink | memcpy() line 117 (arg 1, role=pointer) |
| Sink snippet | memcpy(q, ticket + sizeof(__be32) * 2, klen); |
| Possibly guarded | no |
Dismissed: Source pointer ticket+sizeof(__be32)*2 = ticket+8. Reading klen bytes: since klen <= ticket_len-40, offset 8+klen <= ticket_len-32 < ticket_len, so the read stays within the ticket buffer. Same validation as finding #3.
rxkad_verify_packet() — net/rxrpc/rxkad.c FP confidence=high
The scanner flagged a 32-bit to 16-bit truncation at line 476. The note in the scanner's own finding states: 'flag as false positive if the RHS expression uses masking (& 0xFF, & 0xFFFF) or right-shift that limits the value to the destination width before assignment.' The RHS is `(y >> 16) & 0xffff`, which explicitly masks to 16 bits before assignment to the u16 variable. The shift by 16 followed by AND with 0xffff means only the upper 16 bits of y are extracted, and the result is guaranteed to fit in a u16. There is no truncation hazard here — the masking is intentional and correct. Additionally, the subsequent check `if (cksum == 0) cksum = 1;` and the comparison `if (cksum != sp->hdr.cksum)` are straightforward equality checks, not bounds checks that could be bypassed by a wider value.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohl() line 476 |
| Taint snippet | cksum = (y >> 16) & 0xffff; |
| Tainted var | cksum |
| Truncation | line 476: 32 → 16-bit u16 |
| Sink snippet | cksum = (y >> 16) & 0xffff; |
| Possibly guarded | no |
Dismissed: The scanner's own note says to flag as false positive when masking limits the value to the destination width. The expression `(y >> 16) & 0xffff` explicitly masks to exactly 16 bits before assigning to `u16 cksum`. No counterexample exists where the assignment produces a value outside the u16 range, because the AND with 0xffff mathematically constrains the result to [0, 65535]. The subsequent use of `cksum` is only for an equality comparison against `sp->hdr.cksum` (also a checksum value), with no array indexing or allocation sizing involved. This is a false positive.
rxkad_verify_packet_1() — net/rxrpc/rxkad.c FP confidence=high
The flagged truncation is intentional and safe. The code computes 'buf >> 16', which on a 32-bit value yields at most 16 bits of data (bits 31..16 shifted down to bits 15..0), so the result always fits in a u16 without loss. The subsequent XOR and masking with 0xffff further confirm that only a 16-bit check value is expected. This is a cryptographic checksum comparison, not a bounds calculation, so there is no OOB risk from the truncation.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohl() line 377 |
| Taint snippet | check = buf >> 16; |
| Tainted var | check |
| Truncation | line 377: 32 → 16-bit u16 |
| Sink snippet | check = buf >> 16; |
| Possibly guarded | no |
Dismissed: The RHS expression 'buf >> 16' shifts a 32-bit value right by 16 bits, producing a value in the range [0, 0xFFFF]. Assigning this to a u16 is lossless — no bits are discarded. The scanner note says to flag as false positive when a right-shift limits the value to the destination width, which is exactly the case here. Additionally, the check value is only used in a cryptographic equality comparison (check != 0 after XOR), not as an array index, allocation size, or pointer offset, so even if truncation occurred it would cause no memory safety issue.
rxkad_verify_packet_2() — net/rxrpc/rxkad.c FP confidence=high
The scanner flagged a 32-to-16-bit truncation of `check = buf >> 16`. However, this is intentional and safe: `buf` is a 32-bit value from `ntohl(sechdr->data_size)`, and the upper 16 bits are extracted via `buf >> 16`. After the right-shift, the value is guaranteed to fit in 16 bits (the upper 16 bits of a 32-bit value, after shifting right by 16, always yields a value in [0, 65535]). The assignment to `u16 check` is thus a safe narrowing. The subsequent XOR and mask (`check ^= seq ^ call->call_id; check &= 0xffff;`) further ensure it stays 16-bit. The check `if (check != 0)` is a correct integrity verification. No counterexample can be constructed where the truncation causes a security bypass — the truncation IS the intended protocol check (extracting the high 16 bits of a 32-bit field).
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohl() line 427 |
| Taint snippet | check = buf >> 16; |
| Tainted var | check |
| Truncation | line 427: 32 → 16-bit u16 |
| Sink snippet | check = buf >> 16; |
| Possibly guarded | no |
Dismissed: The right-shift `buf >> 16` on a 32-bit value always produces a value that fits in 16 bits (range 0–65535), so the assignment to `u16 check` is not a lossy truncation in any security-relevant sense. The scanner's concern that 'a later bounds check on check may pass even when the original value would not' is inapplicable here: the high 16 bits of `buf` are extracted deliberately to form a checksum, and the check `if (check != 0)` tests exactly the right quantity. No counterexample exists where the truncation allows an invalid packet to pass verification while a non-truncated check would reject it, because the protocol defines the checksum field as exactly these 16 bits.
sctp_association_init() — net/sctp/associola.c BUG confidence=high
The ep->auth_hmacs_list and ep->auth_chunk_list are set from local socket options (user-space via setsockopt), not directly from network packets. However, their param_hdr.length fields are user-controlled and used as memcpy size arguments into fixed-size destination buffers (asoc->c.auth_hmacs and asoc->c.auth_chunks in the sctp_cookie struct). No bounds check against the destination buffer size is performed before the memcpy, enabling a heap buffer overflow.
Finding #1 — Category B — BUG oob_write
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohs() line 264 |
| Taint snippet | memcpy(asoc->c.auth_hmacs, ep->auth_hmacs_list, |
| Tainted var | ntohs(ep->auth_hmacs_list->param_hdr.len |
| Sink | memcpy() line 264 (arg 2, role=size) |
| Sink snippet | memcpy(asoc->c.auth_hmacs, ep->auth_hmacs_list, |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in sctp_association_init; heap corruption if auth_hmacs_list->param_hdr.length exceeds sizeof(asoc->c.auth_hmacs)
Fix: Before the memcpy, validate that ntohs(ep->auth_hmacs_list->param_hdr.length) <= sizeof(asoc->c.auth_hmacs) and is >= sizeof(struct sctp_paramhdr). If not, either truncate or fail initialization.
CVE pattern: User-controlled length field used in memcpy into fixed-size destination without bounds check; similar to classic SCTP parameter overflow patterns
Finding #2 — Category B — BUG oob_write
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohs() line 267 |
| Taint snippet | memcpy(asoc->c.auth_chunks, ep->auth_chunk_list, |
| Tainted var | ntohs(ep->auth_chunk_list->param_hdr.len |
| Sink | memcpy() line 267 (arg 2, role=size) |
| Sink snippet | memcpy(asoc->c.auth_chunks, ep->auth_chunk_list, |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in sctp_association_init; heap corruption if auth_chunk_list->param_hdr.length exceeds sizeof(asoc->c.auth_chunks)
Fix: Before the memcpy, validate that ntohs(ep->auth_chunk_list->param_hdr.length) <= sizeof(asoc->c.auth_chunks) and is >= sizeof(struct sctp_paramhdr). If not, either truncate or fail initialization.
CVE pattern: User-controlled length field used in memcpy into fixed-size destination without bounds check; similar to classic SCTP parameter overflow patterns
__sctp_auth_cid() — net/sctp/auth.c BUG confidence=high
The function checks for zero-length but does not validate that ntohs(param->param_hdr.length) is within the actual received/allocated buffer size. The computed 'len' drives array indexing into param->chunks[] with no upper bound tied to actual memory allocation, enabling out-of-bounds reads with a crafted peer parameter.
Finding #1 — Category F — BUG oob_read
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 556 |
| Taint snippet | len = ntohs(param->param_hdr.length) - sizeof(struct sctp_paramhdr); |
| Tainted var | len |
| Loop | for_loop line 564 |
| Sink snippet | for (i = 0; !found && i < len; i++) { |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in __sctp_auth_cid; kernel may also crash or disclose heap memory depending on what lies beyond the param buffer
Fix: After computing len, validate it against the actual size of the param buffer: e.g., if the param was received as part of an INIT chunk, ensure ntohs(param->param_hdr.length) does not exceed the enclosing chunk's remaining length. At minimum, cap len to the known allocation size: 'if (ntohs(param->param_hdr.length) < sizeof(struct sctp_paramhdr)) return 0; len = ntohs(param->param_hdr.length) - sizeof(struct sctp_paramhdr); if (len > known_param_buf_size - sizeof(struct sctp_paramhdr)) return 0;'
CVE pattern: Server-supplied length field used as loop bound without buffer-size validation — similar to classic SCTP parameter length OOB read patterns
sctp_auth_asoc_get_hmac() — net/sctp/auth.c MIXED confidence=high
The function reads peer-supplied HMAC parameter data from a network packet. Finding #1 is a real bug: n_elt is derived from a server-supplied length field and is used to bound array iteration over hmacs->hmac_ids[] without validating that n_elt * 2 bytes actually fit within the received buffer — a large server-supplied length could cause OOB reads of hmac_ids[]. Findings #2 and #3 are false positives: sctp_hmac_supported() validates 'id' against ARRAY_SIZE(sctp_hmac_list) before any array access, and the return statement at line 480 is guarded by that check.
Finding #1 — Category F — BUG oob_read
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 475 |
| Taint snippet | n_elt = (ntohs(hmacs->param_hdr.length) - |
| Tainted var | n_elt |
| Loop | for_loop line 477 |
| Sink snippet | for (i = 0; i < n_elt; i++) { |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in sctp_auth_asoc_get_hmac — reading hmac_ids[] beyond the end of the peer_hmacs parameter buffer if the server supplies a crafted length field larger than the actual data
Fix: Before the loop, validate that n_elt does not exceed what the buffer can hold: compute max_elts = (ntohs(hmacs->param_hdr.length) - sizeof(struct sctp_paramhdr)) / sizeof(__be16) and clamp n_elt to min(n_elt, max_elts), AND verify that ntohs(hmacs->param_hdr.length) >= sizeof(struct sctp_paramhdr) to prevent underflow. The actual peer_hmacs allocation size should also be checked against n_elt * sizeof(__be16).
CVE pattern: Server-supplied length field controlling loop iteration count without buffer bounds validation — similar to various SCTP parameter parsing OOB reads
Finding #2 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 478 |
| Taint snippet | id = ntohs(hmacs->hmac_ids[i]); |
| Tainted var | id |
| Subscript | [] line 480 |
| Sink snippet | return &sctp_hmac_list[id]; |
| Possibly guarded | no |
Dismissed: The access at line 480 (return &sctp_hmac_list[id]) is only reached when sctp_hmac_supported(id) returns true at line 479. sctp_hmac_supported() checks id < ARRAY_SIZE(sctp_hmac_list) before any array access, so the subscript is bounded. No counterexample exists that passes the guard and causes OOB. False positive.
Finding #3 — Category C — cross-function via sctp_hmac_supported() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 478 |
| Taint snippet | id = ntohs(hmacs->hmac_ids[i]); |
| Tainted var | id |
| Call site | line 479 — passes id to sctp_hmac_supported() |
| Call snippet | if (sctp_hmac_supported(id)) |
| Subscript (in callee) | [] line 44 |
| Sink snippet | sctp_hmac_list[hmac_id].hmac_len != 0; |
| Possibly guarded | no |
Dismissed: sctp_hmac_supported() is itself the validator: it checks hmac_id < ARRAY_SIZE(sctp_hmac_list) at line 43 before accessing sctp_hmac_list[hmac_id] at line 44. The array access inside sctp_hmac_supported() IS the validation logic — a classic false positive per pattern #2 in the review guidelines. No OOB is possible here.
sctp_auth_asoc_set_default_hmac() — net/sctp/auth.c MIXED confidence=high
Finding #1 is a real bug: n_params is derived from a network-supplied length field without validating it against the actual buffer size, so the loop can iterate beyond the hmac_ids array. Finding #2 is a false positive: sctp_hmac_supported() IS the validation function — it bounds-checks hmac_id against ARRAY_SIZE(sctp_hmac_list) before indexing, so the array access inside it is the validation logic, not a vulnerable sink.
Finding #1 — Category F — BUG oob_read
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 534 |
| Taint snippet | n_params = (ntohs(hmacs->param_hdr.length) - |
| Tainted var | n_params |
| Loop | for_loop line 536 |
| Sink snippet | for (i = 0; i < n_params; i++) { |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in sctp_auth_asoc_set_default_hmac — reading hmac_ids[i] beyond the end of the received packet buffer when length field is inflated
Fix: Before the loop, validate that n_params does not exceed the actual number of elements present in the received parameter. Specifically, cap n_params to (actual_buffer_or_param_length - sizeof(struct sctp_paramhdr)) / sizeof(__be16), where actual_buffer_or_param_length is passed in by the caller or derived from the chunk bounds — not blindly trusted from hmacs->param_hdr.length. For example: size_t max_params = (chunk_end - hmacs->hmac_ids) / sizeof(__be16); if (n_params > max_params) n_params = max_params;
CVE pattern: Network-supplied length field used as loop bound without validation against actual buffer size — similar pattern to CVE-2014-3673 (SCTP parameter length OOB)
Finding #2 — Category C — cross-function via sctp_hmac_supported() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 537 |
| Taint snippet | id = ntohs(hmacs->hmac_ids[i]); |
| Tainted var | id |
| Call site | line 538 — passes id to sctp_hmac_supported() |
| Call snippet | if (sctp_hmac_supported(id)) { |
| Subscript (in callee) | [] line 44 |
| Sink snippet | sctp_hmac_list[hmac_id].hmac_len != 0; |
| Possibly guarded | no |
Dismissed: sctp_hmac_supported() is itself the validation function. It checks 'hmac_id < ARRAY_SIZE(sctp_hmac_list)' before indexing sctp_hmac_list[hmac_id]. No counterexample exists: any hmac_id >= ARRAY_SIZE(sctp_hmac_list) returns false immediately, so the array access is always safe. The access inside sctp_hmac_supported() is the validation logic, not a vulnerable sink — classic false positive per pattern 2/4.
sctp_auth_asoc_verify_hmac_id() — net/sctp/auth.c BUG confidence=medium
The function reads `hmacs->param_hdr.length` from what appears to be a network-received SCTP association parameter structure. The `n_elt` value is derived from `ntohs()` on that length field without any validation against the actual allocated size of `asoc->c.auth_hmacs`. If a malicious peer sends an auth_hmacs parameter with a crafted length field, `n_elt` could exceed the actual number of valid elements, causing `__sctp_auth_find_hmacid()` to read beyond the allocated buffer. However, there may be upstream validation in the SCTP parameter parsing path that constrains this value — the confidence is medium rather than high because the full parsing call chain is not visible here.
Finding #1 — Category F — cross-function via __sctp_auth_find_hmacid() — BUG oob_read
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 511 |
| Taint snippet | n_elt = (ntohs(hmacs->param_hdr.length) - |
| Tainted var | n_elt |
| Call site | line 514 — passes n_elt to __sctp_auth_find_hmacid() |
| Call snippet | return __sctp_auth_find_hmacid(hmacs->hmac_ids, n_elt, hmac_id); |
| Loop | for_loop line 490 |
| Sink snippet | for (i = 0; i < n_elts; i++) { |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in __sctp_auth_find_hmacid() — kernel reads hmac_ids[] beyond the end of the allocated auth_hmacs buffer
Fix: Before computing n_elt, validate that `ntohs(hmacs->param_hdr.length)` is at least `sizeof(struct sctp_paramhdr)` and does not exceed the actual allocated size of `asoc->c.auth_hmacs`. Cap n_elt: e.g., `u16 raw_len = ntohs(hmacs->param_hdr.length); if (raw_len < sizeof(struct sctp_paramhdr) || raw_len > sizeof(asoc->c.auth_hmacs)) return 0; n_elt = (raw_len - sizeof(struct sctp_paramhdr)) >> 1;`
CVE pattern: Network-supplied length field used as loop bound without buffer bounds check — similar to classic SCTP/DCCP parameter parsing OOB reads
sctp_auth_ep_add_chunkid() — net/sctp/auth.c BUG confidence=high
The function uses an equality check (==) instead of a greater-than-or-equal check (>=) to guard the array subscript. Additionally, integer underflow in nchunks is possible if param_len < sizeof(struct sctp_paramhdr). Both issues allow the guard to be bypassed, leading to OOB write via p->chunks[nchunks].
Finding #1 — Category C — BUG oob_write
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 673 |
| Taint snippet | param_len = ntohs(p->param_hdr.length); |
| Tainted var | nchunks |
| Subscript | [] line 678 |
| Sink snippet | p->chunks[nchunks] = chunk_id; |
| Possibly guarded | yes (heuristic) |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in sctp_auth_ep_add_chunkid; heap corruption potentially leading to privilege escalation or kernel panic
Fix: Replace the equality check with a proper range check: (1) validate param_len >= sizeof(struct sctp_paramhdr) before computing nchunks to prevent underflow; (2) change 'if (nchunks == SCTP_AUTH_MAX_CHUNKS)' to 'if (nchunks >= SCTP_AUTH_MAX_CHUNKS)' to catch any out-of-range value, not just exactly the maximum.
CVE pattern: Integer underflow leading to OOB array write — similar pattern to various SCTP parameter parsing CVEs (e.g., CVE-2014-3673 style off-by-one/equality-instead-of-GTE checks)
sctp_auth_make_key_vector() — net/sctp/auth.c BUG confidence=high
The function extracts lengths from network-supplied SCTP parameter headers and uses them as memcpy sizes without verifying that these lengths are bounded by the actual source buffer sizes. The destination allocation is exactly the sum of these lengths, so OOB writes are impossible, but OOB reads from source buffers (random, chunks, hmacs) are possible if a peer sets a parameter length larger than the actual parameter data. Findings about 'new' being tainted (Category A/E on new->data) and cross-function findings about sctp_auth_create_key() are false positives: 'new' is kernel-allocated and sctp_auth_create_key() validates its argument internally.
Finding #1 — Category B — BUG oob_read
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohs() line 196 |
| Taint snippet | random_len = ntohs(random->param_hdr.length); |
| Tainted var | random_len |
| Sink | memcpy() line 207 (arg 2, role=size) |
| Sink snippet | memcpy(new->data, random, random_len); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in sctp_auth_make_key_vector; memcpy reads beyond the actual random parameter buffer if random->param_hdr.length > actual buffer size
Fix: Validate random_len >= sizeof(sctp_random_param) and random_len <= actual received buffer size for the random parameter before using it as a copy size.
CVE pattern: SCTP parameter length field OOB read
Finding #2 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | ntohs() line 196 |
| Taint snippet | random_len = ntohs(random->param_hdr.length); |
| Tainted var | new |
| Sink | memcpy() line 207 (arg 0, role=pointer) |
| Sink snippet | memcpy(new->data, random, random_len); |
| Possibly guarded | no |
Dismissed: False positive: 'new' is kernel-allocated via kmalloc. The taint propagation to new->data is spurious. The pointer arithmetic stays within the allocated region since total allocation equals the sum of all copy sizes.
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 196 |
| Taint snippet | random_len = ntohs(random->param_hdr.length); |
| Tainted var | new |
| Pointer deref | new->data line 207 |
| Sink snippet | memcpy(new->data, random, random_len); |
| Possibly guarded | no |
Dismissed: False positive: new is a kernel-allocated struct, not a server-supplied pointer. Accessing new->data is safe after successful kmalloc.
Finding #4 — Category B — BUG oob_read
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohs() line 199 |
| Taint snippet | chunks_len = ntohs(chunks->param_hdr.length); |
| Tainted var | chunks_len |
| Sink | memcpy() line 211 (arg 2, role=size) |
| Sink snippet | memcpy(new->data + offset, chunks, chunks_len); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in sctp_auth_make_key_vector; memcpy reads beyond the actual chunks parameter buffer if chunks->param_hdr.length > actual buffer size
Fix: Validate chunks_len >= sizeof(sctp_chunks_param) and chunks_len <= actual received buffer size for the chunks parameter before using it as a copy size.
CVE pattern: SCTP parameter length field OOB read
Finding #5 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | ntohs() line 196 |
| Taint snippet | random_len = ntohs(random->param_hdr.length); |
| Tainted var | new |
| Sink | memcpy() line 211 (arg 0, role=pointer) |
| Sink snippet | memcpy(new->data + offset, chunks, chunks_len); |
| Possibly guarded | no |
Dismissed: False positive: new is kernel-allocated. offset = random_len at this point, and the allocation is random_len + chunks_len + hmacs_len, so new->data + offset is within bounds for the chunks copy.
Finding #6 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 196 |
| Taint snippet | random_len = ntohs(random->param_hdr.length); |
| Tainted var | new |
| Pointer deref | new->data line 211 |
| Sink snippet | memcpy(new->data + offset, chunks, chunks_len); |
| Possibly guarded | no |
Dismissed: False positive: new is kernel-allocated; new->data at offset random_len is within the allocation boundary.
Finding #7 — Category B — BUG oob_read
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohs() line 197 |
| Taint snippet | hmacs_len = ntohs(hmacs->param_hdr.length); |
| Tainted var | hmacs_len |
| Sink | memcpy() line 215 (arg 2, role=size) |
| Sink snippet | memcpy(new->data + offset, hmacs, hmacs_len); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in sctp_auth_make_key_vector; memcpy reads beyond the actual hmacs parameter buffer if hmacs->param_hdr.length > actual buffer size
Fix: Validate hmacs_len >= sizeof(sctp_hmac_algo_param) and hmacs_len <= actual received buffer size for the hmacs parameter before using it as a copy size.
CVE pattern: SCTP parameter length field OOB read
Finding #8 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | ntohs() line 196 |
| Taint snippet | random_len = ntohs(random->param_hdr.length); |
| Tainted var | new |
| Sink | memcpy() line 215 (arg 0, role=pointer) |
| Sink snippet | memcpy(new->data + offset, hmacs, hmacs_len); |
| Possibly guarded | no |
Dismissed: False positive: new is kernel-allocated. Destination pointer new->data + offset is within allocated bounds.
Finding #9 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 196 |
| Taint snippet | random_len = ntohs(random->param_hdr.length); |
| Tainted var | new |
| Pointer deref | new->data line 215 |
| Sink snippet | memcpy(new->data + offset, hmacs, hmacs_len); |
| Possibly guarded | no |
Dismissed: False positive: new is kernel-allocated; new->data access at offset = random_len + chunks_len is within the allocated region.
Finding #10 — Category B — cross-function via sctp_auth_create_key() — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohs() line 196 |
| Taint snippet | random_len = ntohs(random->param_hdr.length); |
| Tainted var | len |
| Call site | line 203 — passes len to sctp_auth_create_key() |
| Call snippet | new = sctp_auth_create_key(len, gfp); |
| Sink (in callee) | kmalloc() line 68 (arg 0, role=size_mul_overflow) |
| Sink snippet | key = kmalloc(sizeof(struct sctp_auth_bytes) + key_len, gfp); |
| Possibly guarded | yes (heuristic) |
Dismissed: False positive: sctp_auth_create_key() explicitly checks key_len > (INT_MAX - sizeof(struct sctp_auth_bytes)) and returns NULL on overflow. The allocation size is fully validated inside the callee. No counterexample possible — the guard caps the value before kmalloc.
Finding #11 — Category E — cross-function via sctp_auth_create_key() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 196 |
| Taint snippet | random_len = ntohs(random->param_hdr.length); |
| Tainted var | len |
| Call site | line 203 — passes len to sctp_auth_create_key() |
| Call snippet | new = sctp_auth_create_key(len, gfp); |
| Pointer deref | len-> line 72 |
| Sink snippet | key->len = key_len; |
| Possibly guarded | no |
Dismissed: False positive: key->len = key_len is only reached after kmalloc succeeds, which means key is a valid kernel-allocated pointer. The taint on key_len does not make key->len unsafe to write.
Finding #12 — Category E — cross-function via sctp_auth_create_key() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 196 |
| Taint snippet | random_len = ntohs(random->param_hdr.length); |
| Tainted var | len |
| Call site | line 203 — passes len to sctp_auth_create_key() |
| Call snippet | new = sctp_auth_create_key(len, gfp); |
| Pointer deref | len-> line 73 |
| Sink snippet | refcount_set(&key->refcnt, 1); |
| Possibly guarded | no |
Dismissed: False positive: refcount_set(&key->refcnt, 1) operates on a kernel-allocated struct after successful kmalloc. The tainted key_len value only determines allocation size, not the struct pointer validity.
__sctp_rcv_lookup_endpoint() — net/sctp/input.c FP confidence=high
The hash value is computed by sctp_ep_hashfn(), which is a dedicated hash function that constrains its output to the valid range of sctp_ep_hashtable indices. The lport value, while derived from a network packet, is passed through sctp_ep_hashfn() which performs a modulo operation (or equivalent bitmask) against the hash table size, guaranteeing the result is within bounds. This is a standard hash-table lookup pattern in the kernel where the hash function itself serves as the bounds constraint.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 853 |
| Taint snippet | hash = sctp_ep_hashfn(net, ntohs(lport)); |
| Tainted var | hash |
| Subscript | [] line 854 |
| Sink snippet | head = &sctp_ep_hashtable[hash]; |
| Possibly guarded | no |
Dismissed: The lport value (sin_port) is indeed server-supplied from the network packet, so ntohs() propagates taint. However, the value is immediately passed to sctp_ep_hashfn(), which computes 'hash % sctp_ep_hashsize' (or a bitmask equivalent). This constrains the result to [0, sctp_ep_hashsize-1], which is precisely the valid index range for sctp_ep_hashtable. No counterexample can be constructed: regardless of what lport value arrives from the network (including 0 or 65535), sctp_ep_hashfn() maps it into bounds. The scanner flagged the taint flow but did not model the hash function's range-constraining postcondition, making this a false positive.
sctp_acked() — net/sctp/outqueue.c FP confidence=medium
Both findings involve server-supplied data from SACK packets. Finding #1 is a real truncation but results in a logic error (possible incorrect ack determination), not memory safety. Finding #2's loop count risk is mitigated by SCTP chunk-level validation that occurs earlier in the receive path before sctp_check_transmitted is ever called — the SACK chunk length is validated against num_gap_ack_blocks before the chunk reaches this processing stage.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohl() line 1797 |
| Taint snippet | tsn_offset = tsn - ctsn; |
| Tainted var | tsn_offset |
| Truncation | line 1797: 32 → 16-bit u16 |
| Sink snippet | tsn_offset = tsn - ctsn; |
| Possibly guarded | no |
Dismissed: The truncation of tsn-ctsn to 16-bit tsn_offset is real, but gap block start/end fields are also 16-bit. If tsn-ctsn exceeds 0xFFFF, the truncated value might spuriously match a gap block, leading to incorrect ack logic — but no memory safety violation. This is a protocol correctness concern rather than a security vulnerability.
Finding #2 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 1796 |
| Taint snippet | blocks = ntohs(sack->num_gap_ack_blocks); |
| Tainted var | blocks |
| Loop | for_loop line 1798 |
| Sink snippet | for (i = 0; i < blocks; ++i) { |
| Possibly guarded | no |
Dismissed: SCTP's standard receive path validates SACK chunk length against the number of gap ack blocks and duplicate TSNs in sctp_sf_eat_sack_6_2() before handing the SACK to outqueue processing. This prior full-path validation protects the loop. The call site shown does not display this earlier validation because it occurs several layers up in the receive path, making this a false positive consistent with pattern #3 (prior full-traversal/validation).
sctp_check_transmitted() — net/sctp/outqueue.c FP confidence=high
The static analyzer flagged tsn (from ntohl()) as a tainted loop bound inside sctp_acked(). However, sctp_acked() is itself a validation/query function: it checks whether a given TSN is acknowledged in the SACK. The 'blocks' loop bound inside sctp_acked() is controlled by sack->num_gap_ack_blocks (a separate server-supplied field), NOT by the tsn argument passed in. The tsn argument is only used to compute tsn_offset and compared against frags[i].gab.start/.end — it is not used as a loop bound at all. The loop iterates 'blocks' times regardless of tsn. The scanner confused 'tsn is passed to sctp_acked()' with 'tsn controls the loop bound', but the actual loop bound is 'blocks' (sack->num_gap_ack_blocks), not tsn. Furthermore, tchunk->subh.data_hdr->tsn is a TSN the local kernel previously assigned and stored in a chunk it sent — it is not a server-supplied value being used to index memory.
Finding #1 — Category F — cross-function via sctp_acked() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 1480 |
| Taint snippet | tsn = ntohl(tchunk->subh.data_hdr->tsn); |
| Tainted var | tsn |
| Call site | line 1481 — passes tsn to sctp_acked() |
| Call snippet | if (sctp_acked(sack, tsn)) { |
| Loop | for_loop line 1798 |
| Sink snippet | for (i = 0; i < blocks; ++i) { |
| Possibly guarded | no |
Dismissed: The tsn value at line 1480 is read from tchunk->subh.data_hdr->tsn, which is the TSN the local kernel assigned to an outgoing DATA chunk it constructed and transmitted. It is NOT a server-supplied value. Inside sctp_acked(), the tsn argument is used only to compute tsn_offset = tsn - ctsn and to compare against gap block boundaries — it does NOT control the loop bound. The loop bound is 'blocks = ntohs(sack->num_gap_ack_blocks)', which is a different field entirely. The scanner's analysis is incorrect: tsn does not flow into the loop bound (arg 0 of for_loop). No counterexample can be constructed because tsn is not the loop bound. This is a false positive.
sctp_outq_flush_data() — net/sctp/outqueue.c FP confidence=high
The scanner misidentifies the truncation: 'stream_state' receives '->state' (a small enum field of a stream struct), not a direct truncation of the 16-bit 'sid'. The 16-bit sid is used as an array index in SCTP_SO(), not stored in stream_state. Additionally, outgoing chunks have stream IDs set by the local kernel stack, not by a remote server. The real concern (unvalidated sid as array index) is a different issue not flagged here.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohs() line 1087 |
| Taint snippet | __u8 stream_state = SCTP_SO(&ctx->asoc->stream, sid)->state; |
| Tainted var | stream_state |
| Truncation | line 1087: 16 → 8-bit __u8 |
| Sink snippet | __u8 stream_state = SCTP_SO(&ctx->asoc->stream, sid)->state; |
| Possibly guarded | no |
Dismissed: The scanner incorrectly attributes the truncation: stream_state is assigned from SCTP_SO(...)->state (the state field of a sctp_stream_out struct, a small type), not directly from the 16-bit ntohs() result. No actual 16-bit→8-bit truncation of a meaningful value occurs. The chunks are outgoing data chunks with stream IDs set locally by the kernel, so the data is not server-supplied. No counterexample can be constructed for the stated truncation bug because the assignment path does not involve truncating the ntohs() result into stream_state.
sctp_outq_sack() — net/sctp/outqueue.c BUG confidence=high
The function reads gap_ack_blocks from a network-supplied SACK chunk via ntohs() and immediately uses it as an array index (gap_ack_blocks - 1) into frags[] without any bounds check. The only guard is `if (gap_ack_blocks)` which only checks for non-zero — it does not verify that gap_ack_blocks is within the actual received chunk length. A malicious peer can send a SACK chunk with num_gap_ack_blocks set to an arbitrarily large value, causing the array access frags[gap_ack_blocks - 1] to read memory far beyond the end of the received skb data.
Finding #1 — Category C — BUG oob_read
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 1275 |
| Taint snippet | gap_ack_blocks = ntohs(sack->num_gap_ack_blocks); |
| Tainted var | gap_ack_blocks |
| Subscript | [] line 1320 |
| Sink snippet | highest_tsn += ntohs(frags[gap_ack_blocks - 1].gab.end); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in sctp_outq_sack / kernel panic from reading beyond the end of the received SCTP chunk skb data
Fix: Before using gap_ack_blocks as an array index, validate it against the actual chunk length: ensure that sizeof(struct sctp_sackhdr) + gap_ack_blocks * sizeof(union sctp_sack_variable) <= (chunk->chunk_hdr->length - sizeof(struct sctp_chunkhdr)). For example: if (gap_ack_blocks > (ntohs(chunk->chunk_hdr->length) - sizeof(struct sctp_chunkhdr) - sizeof(struct sctp_sackhdr)) / sizeof(union sctp_sack_variable)) return -EINVAL;
CVE pattern: Out-of-bounds read via unchecked server-supplied count field used as array subscript — similar in class to CVE-2014-7841 and other SCTP chunk parsing OOB bugs
sctp_sack_update_unack_data() — net/sctp/outqueue.c FP confidence=medium
The num_gap_ack_blocks field is server-supplied, but SCTP validates SACK chunks via sctp_verify_sack() in the state machine (sctp_sf_eat_sack_6_2) before sctp_outq_sack() is ever called. That validator checks that the chunk length covers sizeof(sctp_sackhdr_t) + num_gap_ack_blocks*sizeof(gap_ack_block) + num_dup_tsns*4, so the loop in sctp_sack_update_unack_data() cannot iterate beyond the validated buffer. The static scanner missed this upstream validation gate.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 1236 |
| Taint snippet | for (i = 0; i < ntohs(sack->num_gap_ack_blocks); i++) { |
| Tainted var | (inline) |
| Loop | for_loop line 1236 |
| Sink snippet | for (i = 0; i < ntohs(sack->num_gap_ack_blocks); i++) { |
| Possibly guarded | no |
Dismissed: The SCTP state machine calls sctp_verify_sack() before sctp_outq_sack() is invoked. sctp_verify_sack() validates that the SACK chunk's length field is consistent with num_gap_ack_blocks and num_dup_tsns, establishing that the buffer is large enough for the loop. No counterexample can be constructed that passes sctp_verify_sack() yet causes OOB access in the loop — the upstream validation is a sufficient guard.
sctp_get_asconf_response() — net/sctp/sm_make_chunk.c BUG confidence=high
The function uses server-supplied chunk_hdr->length (via ntohs) to control loop iteration without validating it against the actual skb->len. Additionally, the per-element length field from the network is used to advance the pointer without minimum-size checks, enabling infinite loops or OOB reads.
Finding #1 — Category F — BUG oob_read
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 3429 |
| Taint snippet | asconf_ack_len = ntohs(asconf_ack->chunk_hdr->length) - |
| Tainted var | asconf_ack_len |
| Loop | while_loop line 3440 |
| Sink snippet | while (asconf_ack_len > 0) { |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in sctp_get_asconf_response; kernel reads past the end of skb->data when server-supplied chunk_hdr->length exceeds actual received data size
Fix: Cap asconf_ack_len to min(ntohs(asconf_ack->chunk_hdr->length) - sizeof(sctp_chunkhdr), asconf_ack->skb->len - sizeof(sctp_addiphdr)). Additionally, in the loop body, validate that 'length' from ntohs(asconf_ack_param->param_hdr.length) is at least sizeof(struct sctp_addip_param) before advancing the pointer, to prevent infinite loops and ensure the pointer stays within the buffer.
CVE pattern: SCTP chunk length field OOB read — similar to CVE-2014-3673/CVE-2014-3687 style SCTP chunk parsing vulnerabilities
sctp_pack_cookie() — net/sctp/sm_make_chunk.c FP confidence=medium
The destination buffer is allocated using the same ntohs(init_chunk->chunk_hdr->length) value that is later used as the memcpy size, so destination OOB write is impossible by construction. For source OOB read, the SCTP receive path validates chunk lengths against actual skb data before delivering chunks to this code path. The finding is a false positive, though the implicit reliance on upstream validation for source bounds is worth documenting.
Finding #1 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohs() line 1704 |
| Taint snippet | memcpy(cookie + 1, init_chunk->chunk_hdr, |
| Tainted var | ntohs(init_chunk->chunk_hdr->length) |
| Sink | memcpy() line 1704 (arg 2, role=size) |
| Sink snippet | memcpy(cookie + 1, init_chunk->chunk_hdr, |
| Possibly guarded | no |
Dismissed: The destination buffer is kzalloc'd with size = headersize + bodysize, where bodysize includes ntohs(init_chunk->chunk_hdr->length). So the destination always has enough room — no counterexample possible for OOB write. For OOB read from the source (init_chunk->chunk_hdr), the SCTP stack validates that chunk_hdr->length fits within the received skb during chunk parsing before this function is called. No counterexample found that would pass upstream validation yet cause OOB here.
sctp_process_asconf() — net/sctp/sm_make_chunk.c BUG confidence=medium
sctp_process_asconf() computes chunk_len from a server-supplied length field and then multiplies by 4 before passing to sctp_make_asconf_ack(). There are no bounds checks to ensure chunk_len is non-negative or that chunk_len*4 doesn't overflow. The flagged sink (ptr_deref at line 2999) is actually safe since retval is null-checked, but the underlying issue is an undersized or incorrectly-sized allocation due to unvalidated arithmetic on server-supplied data.
Finding #1 — Category E — cross-function via sctp_make_asconf_ack() — BUG undersized_alloc
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 3283 |
| Taint snippet | chunk_len = ntohs(asconf->chunk_hdr->length) - |
| Tainted var | chunk_len |
| Call site | line 3304 — passes chunk_len to sctp_make_asconf_ack() |
| Call snippet | asconf_ack = sctp_make_asconf_ack(asoc, serial, chunk_len * 4); |
| Pointer deref | chunk_len-> line 2999 |
| Sink snippet | retval->subh.addip_hdr = |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: Heap corruption or BUG: KASAN: slab-out-of-bounds when writing ASCONF_ACK parameters into an undersized buffer, or integer overflow in chunk_len*4 causing wrong allocation size
Fix: Validate chunk_len after each subtraction to ensure it remains non-negative (return error if chunk_len < 0). Also check for integer overflow before computing chunk_len * 4, e.g., if (chunk_len < 0 || chunk_len > (INT_MAX/4)) return NULL/error. Ensure addr_param->p.length is validated against remaining buffer size before subtracting.
CVE pattern: Integer underflow in network-supplied length field leading to undersized allocation, similar to CVE-2014-4667 style SCTP length field issues
sctp_process_asconf_ack() — net/sctp/sm_make_chunk.c BUG confidence=medium
The function processes a locally-stored ASCONF chunk (kernel-built) but reads TLV lengths from it without proper bounds validation. The critical missing checks are: (1) no validation that addr_param->p.length is sane (>= sizeof(*addr_param) and <= asconf_len) before computing asconf_param, (2) no check that asconf_param->param_hdr.length > 0 in the loop, which can cause an infinite loop, (3) no check that asconf_param + sizeof(*asconf_param) stays within the skb buffer bounds. Finding #3 is a false positive on loop-bound characterization.
Finding #1 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 3491 |
| Taint snippet | length = ntohs(addr_param->p.length); |
| Tainted var | asconf_param |
| Pointer deref | asconf_param->param_hdr line 3529 |
| Sink snippet | asconf_param->param_hdr.type; |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in sctp_process_asconf_ack when addr_param->p.length is zero or exceeds buffer bounds, causing asconf_param to point outside the skb buffer
Fix: Validate that ntohs(addr_param->p.length) >= sizeof(*addr_param) and <= asconf_len before computing asconf_param. Inside the loop, similarly validate that ntohs(asconf_param->param_hdr.length) >= sizeof(*asconf_param) and that asconf_param + length stays within the original skb buffer before advancing the pointer.
CVE pattern: TLV length field not validated before pointer arithmetic — similar pattern to CVE-2014-3673 (SCTP OOB access)
Finding #2 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 3491 |
| Taint snippet | length = ntohs(addr_param->p.length); |
| Tainted var | asconf_param |
| Pointer deref | asconf_param->param_hdr line 3542 |
| Sink snippet | length = ntohs(asconf_param->param_hdr.length); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: Infinite loop or kernel panic if asconf_param->param_hdr.length is 0, causing asconf_len to never decrease; or OOB read if asconf_param advances past skb end
Fix: Before the length assignment at line 3542, verify that asconf_param is still within bounds (asconf_param + sizeof(*asconf_param) <= skb_tail_pointer(asconf->skb)), and that ntohs(asconf_param->param_hdr.length) >= sizeof(*asconf_param) to prevent zero-length infinite loops.
CVE pattern: TLV zero-length infinite loop — similar to multiple SCTP CVEs
Finding #3 — Category F — cross-function via sctp_get_asconf_response() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 3491 |
| Taint snippet | length = ntohs(addr_param->p.length); |
| Tainted var | asconf_param |
| Call site | line 3508 — passes asconf_param to sctp_get_asconf_response() |
| Call snippet | err_code = sctp_get_asconf_response(asconf_ack, |
| Loop | while_loop line 3440 |
| Sink snippet | while (asconf_ack_len > 0) { |
| Possibly guarded | no |
Dismissed: False positive on loop-bound characterization. In sctp_get_asconf_response(), the while loop at line 3440 is bounded by asconf_ack_len (independently computed from asconf_ack->chunk_hdr->length), not by the tainted asconf_param pointer. The tainted asconf_param is only used to match crr_id — it controls which response is returned, not the iteration count. The real risk is the OOB pointer dereference of asconf_param itself (covered by findings #1 and #2).
Finding #4 — Category E — cross-function via sctp_asconf_param_success() — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 3491 |
| Taint snippet | length = ntohs(addr_param->p.length); |
| Tainted var | asconf_param |
| Call site | line 3517 — passes asconf_param to sctp_asconf_param_success() |
| Call snippet | sctp_asconf_param_success(asoc, asconf_param); |
| Pointer deref | asconf_param-> line 3366 |
| Sink snippet | af = sctp_get_af_specific(param_type2af(addr_param->p.type)); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in sctp_asconf_param_success when asconf_param points beyond skb buffer, causing addr_param->p.type read from invalid memory
Fix: Validate that asconf_param + sizeof(*asconf_param) + sizeof(union sctp_addr_param) is within the skb buffer before calling sctp_asconf_param_success(). This is a consequence of the same missing bounds check in findings #1/#2.
CVE pattern: TLV length field not validated before pointer arithmetic
Finding #5 — Category E — cross-function via sctp_asconf_param_success() — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 3491 |
| Taint snippet | length = ntohs(addr_param->p.length); |
| Tainted var | asconf_param |
| Call site | line 3517 — passes asconf_param to sctp_asconf_param_success() |
| Call snippet | sctp_asconf_param_success(asoc, asconf_param); |
| Pointer deref | asconf_param-> line 3367 |
| Sink snippet | if (!af->from_addr_param(&addr, addr_param, htons(bp->port), 0)) |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: NULL pointer dereference or OOB read in sctp_asconf_param_success when af is NULL (from invalid addr_param->p.type) or addr_param points outside buffer
Fix: Same fix as finding #4 — validate buffer bounds before calling sctp_asconf_param_success(). Note that sctp_get_af_specific() may return NULL for an invalid type, but the code checks af indirectly via the function pointer call without a NULL check shown (though from_addr_param dispatch may handle it).
CVE pattern: TLV length field not validated before pointer arithmetic
sctp_process_ext_param() — net/sctp/sm_make_chunk.c BUG confidence=high
sctp_process_ext_param() computes num_ext purely from the server-supplied param.p->length field without validating that the declared length fits within the actual received buffer. The call site in sctp_process_param() does not perform any length validation before the call for the SCTP_PARAM_SUPPORTED_EXT case. Additionally, if param.p->length is smaller than sizeof(struct sctp_paramhdr), the unsigned subtraction wraps around, producing a huge iteration count.
Finding #1 — Category F — BUG oob_read
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 2034 |
| Taint snippet | __u16 num_ext = ntohs(param.p->length) - sizeof(struct sctp_paramhdr); |
| Tainted var | num_ext |
| Loop | for_loop line 2037 |
| Sink snippet | for (i = 0; i < num_ext; i++) { |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in sctp_process_ext_param; reading beyond the packet buffer when a malicious peer sends SCTP_PARAM_SUPPORTED_EXT with an inflated or underflowing length field
Fix: Before computing num_ext, validate that ntohs(param.p->length) >= sizeof(struct sctp_paramhdr); then validate that sizeof(struct sctp_paramhdr) + num_ext does not exceed the actual buffer/chunk length passed to the function. Clamp or reject the parameter if the declared length exceeds the real data available. Example: if (ntohs(param.p->length) < sizeof(struct sctp_paramhdr)) return; num_ext = ntohs(param.p->length) - sizeof(struct sctp_paramhdr); and ensure num_ext does not exceed the remaining validated buffer size.
CVE pattern: Server-supplied length field used as loop bound without buffer validation — similar pattern to various SCTP/network protocol OOB read CVEs
sctp_process_param() — net/sctp/sm_make_chunk.c FP confidence=high
sctp_process_param receives parameters already validated by sctp_walk_params in the caller. The loop iteration count 'sat' is derived from param.p->length which sctp_walk_params has already verified fits within the actual packet buffer. Therefore types[0..sat-1] lies within the validated buffer extent.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 2600 |
| Taint snippet | sat = ntohs(param.p->length) - sizeof(struct sctp_paramhdr); |
| Tainted var | sat |
| Loop | for_loop line 2604 |
| Sink snippet | for (i = 0; i < sat; ++i) { |
| Possibly guarded | no |
Dismissed: The sctp_walk_params macro in the caller (sctp_process_init) validates each parameter's length against the actual chunk buffer before yielding it. sat = (ntohs(param.p->length) - sizeof(struct sctp_paramhdr)) / sizeof(__u16) is therefore bounded by the real buffer size. Accessing param.sat->types[i] for i < sat stays within the validated parameter bounds. No counterexample is constructible because any inflated param.p->length would have caused sctp_walk_params to reject the parameter before it reached this function.
sctp_verify_ext_param() — net/sctp/sm_make_chunk.c BUG confidence=high
sctp_verify_ext_param() computes num_ext from a server-supplied length field without validating that the resulting iteration count stays within the actual bounds of the parameter buffer. Two issues: (1) unsigned underflow if length < sizeof(sctp_paramhdr), producing a huge num_ext; (2) even with a valid subtraction, num_ext is never checked against the actual bytes available in param.ext->chunks[], allowing out-of-bounds reads.
Finding #1 — Category F — BUG oob_read
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 2000 |
| Taint snippet | __u16 num_ext = ntohs(param.p->length) - sizeof(struct sctp_paramhdr); |
| Tainted var | num_ext |
| Loop | for_loop line 2005 |
| Sink snippet | for (i = 0; i < num_ext; i++) { |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in sctp_verify_ext_param when processing a malformed SCTP INIT/INIT-ACK with an oversized or undersized SUPPORTED_EXT parameter length field
Fix: Before the loop, validate that param.p->length >= sizeof(struct sctp_paramhdr) (to prevent underflow), then validate that num_ext fits within the actual parameter data: num_ext = ntohs(param.p->length) - sizeof(struct sctp_paramhdr); and ensure num_ext <= (actual_param_buffer_end - param.ext->chunks). A simple fix: if (ntohs(param.p->length) < sizeof(struct sctp_paramhdr)) return 1; and then clamp/check num_ext against the parameter's declared length vs. remaining buffer size passed from the caller.
CVE pattern: Server-supplied length field used as loop bound without bounds validation — similar to CVE-2014-0101 and other SCTP parameter parsing OOB reads
sctp_verify_param() — net/sctp/sm_make_chunk.c BUG confidence=high
sctp_verify_param() validates several parameter types for correct lengths, but the SCTP_PARAM_HMAC_ALGO case fails to validate that param.p->length >= sizeof(struct sctp_paramhdr) before computing n_elt, allowing underflow/wrap-around of the 16-bit arithmetic to produce a very large iteration count.
Finding #1 — Category F — BUG oob_read
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 2249 |
| Taint snippet | n_elt = (ntohs(param.p->length) - |
| Tainted var | n_elt |
| Loop | for_loop line 2256 |
| Sink snippet | for (i = 0; i < n_elt; i++) { |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in sctp_verify_param / kernel crash reading past HMAC parameter buffer with a malformed SCTP INIT chunk
Fix: Before computing n_elt, validate that ntohs(param.p->length) >= sizeof(struct sctp_paramhdr). Then validate that n_elt * sizeof(__be16) <= ntohs(param.p->length) - sizeof(struct sctp_paramhdr). E.g.: u16 plen = ntohs(param.p->length); if (plen < sizeof(struct sctp_paramhdr)) { /* abort */ } n_elt = (plen - sizeof(struct sctp_paramhdr)) >> 1;
CVE pattern: Integer underflow leading to oversized loop iteration count from malformed network parameter length field
sctp_eat_data() — net/sctp/sm_statefuns.c FP confidence=high
Both call sites validate chunk length via sctp_chunk_length_valid() before calling sctp_eat_data(). Inside sctp_eat_data(), tsn undergoes sctp_tsnmap_check() which bounds-checks the TSN range. The flagged sink (retval->transport = chunk->transport) in sctp_make_abort_no_data() is completely independent of the tsn parameter - tsn is only used as a data payload via htonl(tsn), never as a pointer or index. The scanner made a spurious taint propagation connection.
Finding #1 — Category E — cross-function via sctp_make_abort_no_data() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 6548 |
| Taint snippet | tsn = ntohl(data_hdr->tsn); |
| Tainted var | tsn |
| Call site | line 6658 — passes tsn to sctp_make_abort_no_data() |
| Call snippet | err = sctp_make_abort_no_data(asoc, chunk, tsn); |
| Pointer deref | tsn-> line 993 |
| Sink snippet | retval->transport = chunk->transport; |
| Possibly guarded | yes (heuristic) |
Dismissed: The tsn value is server-supplied but is only used as a 32-bit data payload in sctp_make_abort_no_data(). The code does: payload = htonl(tsn), then sctp_addto_chunk with sizeof(payload). The sink retval->transport = chunk->transport at line 993 dereferences 'chunk' (the received chunk pointer), which has no data-flow dependency on 'tsn'. The scanner incorrectly attributed the chunk->transport dereference to the tsn taint. No counterexample is possible because tsn never influences any pointer arithmetic or array indexing in this callee. This is a classic false positive from imprecise taint propagation that conflates all parameters of a function call.
sctp_sf_authenticate() — net/sctp/sm_statefuns.c FP confidence=medium
The function has reasonable validation: hmac_id is validated by sctp_auth_asoc_verify_hmac_id before array indexing; sig_len is constrained to equal hmac->hmac_len (a fixed small value) before memory operations. The main concern is the sctp_auth_chunk_verify call path which lacks an explicit chunk_length_valid check, potentially allowing integer underflow in sig_len computation.
Finding #1 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohs() line 4436 |
| Taint snippet | sig_len = ntohs(chunk->chunk_hdr->length) - |
| Tainted var | sig_len |
| Sink | memset() line 4456 (arg 2, role=size) |
| Sink snippet | memset(digest, 0, sig_len); |
| Possibly guarded | yes (heuristic) |
Dismissed: In sctp_sf_eat_auth, sctp_chunk_length_valid guards against underflow, and sig_len != hmac->hmac_len constrains sig_len to a fixed small value. In sctp_auth_chunk_verify, no explicit chunk_length_valid is visible, but the memory operations are still bounded by hmac_len. No concrete counterexample could be constructed for the eat_auth path. The auth_chunk_verify path is slightly less protected but the data was previously parsed.
Finding #2 — Category C — cross-function via sctp_auth_get_hmac() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 4438 |
| Taint snippet | hmac = sctp_auth_get_hmac(ntohs(auth_hdr->hmac_id)); |
| Tainted var | ntohs(auth_hdr->hmac_id) |
| Call site | line 4438 — passes ntohs(auth_hdr->hmac_id) to sctp_auth_get_hmac() |
| Call snippet | hmac = sctp_auth_get_hmac(ntohs(auth_hdr->hmac_id)); |
| Subscript (in callee) | [] line 450 |
| Sink snippet | return &sctp_hmac_list[hmac_id]; |
| Possibly guarded | no |
Dismissed: sctp_auth_asoc_verify_hmac_id() is called at line 4420 BEFORE sctp_auth_get_hmac() at line 4438, validating the hmac_id. This validator ensures the hmac_id is in the association's approved list, which should only contain valid IDs bounded by SCTP_AUTH_HMAC_ID_MAX. The array subscript in sctp_auth_get_hmac() is protected by this prior validation gate. No counterexample can be constructed that passes the validator but causes OOB.
sctp_sf_do_5_1B_init() — net/sctp/sm_statefuns.c FP confidence=high
The 'tainted' value len comes from ntohs(err_chunk->chunk_hdr->length), but err_chunk is a locally-constructed kernel ERROR chunk built by sctp_verify_init(), not a server-supplied buffer. The chunk_hdr->length was written by the kernel. Furthermore, even if len were server-supplied, the flagged sinks in sctp_make_init_ack() (lines 478, 482, 484) are field accesses on a freshly allocated retval chunk — they are NOT pointer dereferences offset by len. The static analyzer incorrectly propagated taint from len through the chunksize arithmetic to unrelated pointer dereferences on retval.
Finding #1 — Category E — cross-function via sctp_make_init_ack() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 442 |
| Taint snippet | len = ntohs(err_chunk->chunk_hdr->length) - |
| Tainted var | len |
| Call site | line 445 — passes len to sctp_make_init_ack() |
| Call snippet | repl = sctp_make_init_ack(new_asoc, chunk, GFP_ATOMIC, len); |
| Pointer deref | len-> line 478 |
| Sink snippet | retval->transport = |
| Possibly guarded | no |
Dismissed: err_chunk is locally constructed by the kernel via sctp_verify_init(). The sink at line 478 (retval->transport) is a fixed field access on a freshly allocated chunk, not derived from len. Taint propagation is spurious.
Finding #2 — Category E — cross-function via sctp_make_init_ack() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 442 |
| Taint snippet | len = ntohs(err_chunk->chunk_hdr->length) - |
| Tainted var | len |
| Call site | line 445 — passes len to sctp_make_init_ack() |
| Call snippet | repl = sctp_make_init_ack(new_asoc, chunk, GFP_ATOMIC, len); |
| Pointer deref | len-> line 482 |
| Sink snippet | retval->subh.init_hdr = |
| Possibly guarded | no |
Dismissed: Same as finding #1. retval->subh.init_hdr access at line 482 is on the locally allocated retval chunk, not offset by len. False positive due to spurious taint propagation through chunksize arithmetic.
Finding #3 — Category E — cross-function via sctp_make_init_ack() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 442 |
| Taint snippet | len = ntohs(err_chunk->chunk_hdr->length) - |
| Tainted var | len |
| Call site | line 445 — passes len to sctp_make_init_ack() |
| Call snippet | repl = sctp_make_init_ack(new_asoc, chunk, GFP_ATOMIC, len); |
| Pointer deref | len-> line 484 |
| Sink snippet | retval->param_hdr.v = sctp_addto_chunk(retval, addrs_len, addrs.v); |
| Possibly guarded | no |
Dismissed: retval->param_hdr.v at line 484 uses addrs_len (not len/unkparam_len) as the size argument. The taint of len does not affect this pointer dereference. False positive.
Finding #4 — Category E — cross-function via sctp_make_init_ack() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 442 |
| Taint snippet | len = ntohs(err_chunk->chunk_hdr->length) - |
| Tainted var | len |
| Call site | line 445 — passes len to sctp_make_init_ack() |
| Call snippet | repl = sctp_make_init_ack(new_asoc, chunk, GFP_ATOMIC, len); |
| Pointer deref | len-> line 516 |
| Sink snippet | retval->asoc = (struct sctp_association *) asoc; |
| Possibly guarded | no |
Dismissed: retval->asoc assignment at line 516 is a fixed field write on the locally allocated retval chunk. No relationship to len whatsoever. The static analyzer over-propagated taint. False positive.
sctp_sf_do_unexpected_init() — net/sctp/sm_statefuns.c FP confidence=high
The taint source is ntohs(err_chunk->chunk_hdr->length) where err_chunk is a locally kernel-constructed ERROR chunk, not a direct copy from a received network packet. Even treating it as tainted, the flagged sinks in sctp_make_init_ack() (lines 478, 482, 484, 516) are dereferences of 'retval', a locally allocated chunk struct returned by sctp_make_control(). The len/unkparam_len parameter influences the allocation size calculation but does not control the pointer being dereferenced at those specific lines. The scanner performed overly aggressive transitive taint propagation through the allocation function.
Finding #1 — Category E — cross-function via sctp_make_init_ack() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 1665 |
| Taint snippet | len = ntohs(err_chunk->chunk_hdr->length) - |
| Tainted var | len |
| Call site | line 1669 — passes len to sctp_make_init_ack() |
| Call snippet | repl = sctp_make_init_ack(new_asoc, chunk, GFP_ATOMIC, len); |
| Pointer deref | len-> line 478 |
| Sink snippet | retval->transport = |
| Possibly guarded | no |
Dismissed: err_chunk is kernel-constructed (built by sctp_verify_init to report unknown params). The sink at line 478 dereferences 'retval', a locally allocated chunk, not a pointer derived from len. False positive due to overly aggressive taint propagation through sctp_make_control() return value.
Finding #2 — Category E — cross-function via sctp_make_init_ack() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 1665 |
| Taint snippet | len = ntohs(err_chunk->chunk_hdr->length) - |
| Tainted var | len |
| Call site | line 1669 — passes len to sctp_make_init_ack() |
| Call snippet | repl = sctp_make_init_ack(new_asoc, chunk, GFP_ATOMIC, len); |
| Pointer deref | len-> line 482 |
| Sink snippet | retval->subh.init_hdr = |
| Possibly guarded | no |
Dismissed: Same as finding #1. The sink at line 482 (retval->subh.init_hdr) dereferences the locally allocated 'retval', not a pointer derived from len. False positive.
Finding #3 — Category E — cross-function via sctp_make_init_ack() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 1665 |
| Taint snippet | len = ntohs(err_chunk->chunk_hdr->length) - |
| Tainted var | len |
| Call site | line 1669 — passes len to sctp_make_init_ack() |
| Call snippet | repl = sctp_make_init_ack(new_asoc, chunk, GFP_ATOMIC, len); |
| Pointer deref | len-> line 484 |
| Sink snippet | retval->param_hdr.v = sctp_addto_chunk(retval, addrs_len, addrs.v); |
| Possibly guarded | no |
Dismissed: Same as finding #1. The sink at line 484 (retval->param_hdr.v) dereferences the locally allocated 'retval', not a pointer derived from len. False positive.
Finding #4 — Category E — cross-function via sctp_make_init_ack() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 1665 |
| Taint snippet | len = ntohs(err_chunk->chunk_hdr->length) - |
| Tainted var | len |
| Call site | line 1669 — passes len to sctp_make_init_ack() |
| Call snippet | repl = sctp_make_init_ack(new_asoc, chunk, GFP_ATOMIC, len); |
| Pointer deref | len-> line 516 |
| Sink snippet | retval->asoc = (struct sctp_association *) asoc; |
| Possibly guarded | no |
Dismissed: Same as finding #1. The sink at line 516 (retval->asoc) dereferences the locally allocated 'retval', not a pointer derived from len. False positive from transitive taint propagation.
sctp_get_port_local() — net/sctp/socket.c FP confidence=high
The value 'snum' comes from ntohs(addr->v4.sin_port), which is a user-supplied port number (not strictly server-supplied in the network-response sense). However, the array index is not 'snum' directly — it passes through sctp_phashfn(net, snum) which is a hash function. sctp_phashfn computes a bounded hash (modulo the hash table size), so the result is always within the bounds of sctp_port_hashtable regardless of what snum is. The scanner flagged snum as the subscript, but the actual subscript is the output of a bounded hash function. Additionally, in the snum==0 branch (line 8423), snum is replaced by 'rover' which is constrained to [low, high] by inet_sk_get_local_port_range and the loop guard. In the else branch (line 8461), snum is an unsigned short (0–65535) passed through sctp_phashfn which masks it to the hash table size. No OOB access is possible.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 8419 |
| Taint snippet | snum = ntohs(addr->v4.sin_port); |
| Tainted var | snum |
| Subscript | [] line 8468 |
| Sink snippet | head = &sctp_port_hashtable[sctp_phashfn(net, snum)]; |
| Possibly guarded | yes (heuristic) |
Dismissed: The actual array subscript is sctp_phashfn(net, snum), not snum itself. sctp_phashfn computes a bounded hash modulo the hash table size (typically using a bitmask or modulo operation on the port number), so the index is always within bounds of sctp_port_hashtable. The scanner incorrectly attributed the subscript role to the raw 'snum' value rather than the bounded hash result. No counterexample can be constructed because the hash function output is inherently bounded. This is a false positive.
sctp_getsockopt_hmac_ident() — net/sctp/socket.c FP confidence=medium
ep->auth_hmacs_list is constructed internally by the SCTP authentication subsystem (sctp_auth_init_hmacs and related functions), not received from a remote peer. The param_hdr.length field is set by the kernel using htons() when building the local parameter list. Therefore, ntohs() reads back a kernel-written value, not server-supplied data. The loop bound num_idents reflects the actual number of HMAC identifiers the kernel stored in the structure, and the copy_to_user operations are additionally guarded by the data_len check against the user optlen.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 6974 |
| Taint snippet | data_len = ntohs(hmacs->param_hdr.length) - |
| Tainted var | num_idents |
| Loop | for_loop line 6987 |
| Sink snippet | for (i = 0; i < num_idents; i++) { |
| Possibly guarded | no |
Dismissed: ep->auth_hmacs_list is a kernel-internal structure initialized by sctp_auth_init_hmacs(). Its param_hdr.length is set by the kernel itself (not from any received network packet), so ntohs() here is reading back a locally-written value. num_idents therefore reflects the actual count of HMAC IDs stored in the array, making the loop safe. No counterexample can be constructed from external input. The check at line 6977 additionally ensures the user-space destination buffer is large enough for the copy_to_user operations.
sctp_getsockopt_local_addrs() — net/sctp/socket.c FP confidence=high
bytes_copied is an internally accumulated counter, not user/server-supplied data. It is bounded by space_left (the allocation size of addrs) through either explicit per-iteration space_left checks in the list loop or through sctp_copy_laddrs which receives space_left as its capacity limit. No counterexample can be constructed where bytes_copied exceeds sizeof(addrs).
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 6392 |
| Taint snippet | if (copy_to_user(to, addrs, bytes_copied)) { |
| Tainted var | bytes_copied |
| Unvalidated size | copy_to_user() arg 2 line 6392 — size bytes_copied |
| Sink snippet | if (copy_to_user(to, addrs, bytes_copied)) { |
| Possibly guarded | no |
Dismissed: bytes_copied is kernel-computed, not user-supplied. In the list_for_each_entry path, each increment is guarded by 'if (space_left < addrlen) return -ENOMEM' and space_left is decremented accordingly, ensuring bytes_copied never exceeds the allocation size of addrs (space_left). In the sctp_copy_laddrs path, the same space_left capacity is passed as a bound parameter. No counterexample exists where bytes_copied > sizeof(addrs). The copy_to_user reads from a kernel buffer (addrs) bounded by space_left and writes to user space — no kernel OOB possible.
sctp_getsockopt_local_auth_chunks() — net/sctp/socket.c BUG confidence=high
The function computes num_chunks from a network-supplied length field without (a) checking for integer underflow when subtracting sizeof(sctp_paramhdr), and (b) bounding num_chunks against the actual size of the source buffer ch. The destination-side guard at line 7112 is present but insufficient because an underflowed num_chunks causes sizeof(sctp_authchunks)+num_chunks to wrap, potentially bypassing the check, and there is no source-buffer size validation at all.
Finding #1 — Category B — BUG oob_read
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohs() line 7111 |
| Taint snippet | num_chunks = ntohs(ch->param_hdr.length) - sizeof(struct sctp_paramhdr); |
| Tainted var | num_chunks |
| Sink | copy_to_user() line 7115 (arg 2, role=size) |
| Sink snippet | if (copy_to_user(to, ch->chunks, num_chunks)) |
| Possibly guarded | yes (heuristic) |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in sctp_getsockopt_local_auth_chunks (heap OOB read from ch->chunks with attacker-controlled length); potential kernel memory disclosure to userspace via copy_to_user
Fix: Before the subtraction, validate that ntohs(ch->param_hdr.length) >= sizeof(struct sctp_paramhdr). Additionally, validate that num_chunks does not exceed the actual allocated size of ch minus sizeof(sctp_paramhdr) (e.g., check against a stored length or a maximum constant). Use: u16 raw_len = ntohs(ch->param_hdr.length); if (raw_len < sizeof(struct sctp_paramhdr)) return -EINVAL; num_chunks = raw_len - sizeof(struct sctp_paramhdr); and then also bound num_chunks against the known maximum chunk list size.
CVE pattern: Integer underflow in length field subtraction leading to OOB read, similar to CVE-2021-3655 (SCTP information disclosure) pattern
sctp_getsockopt_peer_auth_chunks() — net/sctp/socket.c BUG confidence=medium
The function validates the destination (user buffer) side via the len check, but does not validate the source side: num_chunks is derived from a peer-supplied length field and used to read from ch->chunks without verifying that the allocation/received data actually contains num_chunks bytes. An integer underflow is mitigated by unsigned arithmetic causing the len check to catch it. The primary issue is OOB read of the kernel heap when the peer supplies a length field larger than the actual allocated chunk data.
Finding #1 — Category B — BUG oob_read
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | ntohs() line 7061 |
| Taint snippet | num_chunks = ntohs(ch->param_hdr.length) - sizeof(struct sctp_paramhdr); |
| Tainted var | num_chunks |
| Sink | copy_to_user() line 7065 (arg 2, role=size) |
| Sink snippet | if (copy_to_user(to, ch->chunks, num_chunks)) |
| Possibly guarded | yes (heuristic) |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in sctp_getsockopt_peer_auth_chunks — reading beyond the allocated peer_chunks buffer when the peer supplies an inflated param_hdr.length value
Fix: Before computing num_chunks, validate that ntohs(ch->param_hdr.length) >= sizeof(struct sctp_paramhdr) (to prevent underflow) and that num_chunks does not exceed the actual allocated/received size of the ch buffer (i.e., the total parameter length as received on the wire). Add: if (ntohs(ch->param_hdr.length) < sizeof(struct sctp_paramhdr)) return -EINVAL; and ensure num_chunks is capped at the known-good size of ch.
CVE pattern: Heap OOB read via peer-controlled length field — similar in class to CVE-2021-3772 style SCTP parameter length issues
sctp_process_strreset_addstrm_in() — net/sctp/stream.c FP confidence=high
Both findings are false positives. The sequence-number guards at lines 867-870 tightly constrain `request_seq` to be within 2 of `asoc->strreset_inseq`, and the else-if at line 871 further restricts it to [strreset_inseq-2, strreset_inseq-1]. This means `i = asoc->strreset_inseq - request_seq - 1` can only be 0 or 1, which fits safely in a u16 and is within the 2-element `strreset_result` array. No counterexample can be constructed that passes all guards yet produces an out-of-bounds index.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohl() line 872 |
| Taint snippet | i = asoc->strreset_inseq - request_seq - 1; |
| Tainted var | i |
| Truncation | line 872: 32 → 16-bit u16 |
| Sink snippet | i = asoc->strreset_inseq - request_seq - 1; |
| Possibly guarded | no |
Dismissed: The combined guards at lines 867-870 and 871 constrain request_seq to [strreset_inseq-2, strreset_inseq-1], so the difference strreset_inseq - request_seq - 1 is always 0 or 1. Truncation from 32-bit to u16 is completely safe. No counterexample exists where a value passes all guards and the truncation would produce a different result.
Finding #2 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohl() line 866 |
| Taint snippet | request_seq = ntohl(addstrm->request_seq); |
| Tainted var | i |
| Subscript | [] line 873 |
| Sink snippet | result = asoc->strreset_result[i]; |
| Possibly guarded | no |
Dismissed: strreset_result is a 2-element array. The sequence-number guards guarantee i is 0 or 1. No counterexample can pass all guards and produce i >= 2. The scanner missed that TSN_lt-based checks on request_seq bound the arithmetic difference to at most 2.
sctp_process_strreset_addstrm_out() — net/sctp/stream.c FP confidence=high
The TSN sequence number guards at lines 795-803 collectively constrain the subtraction (asoc->strreset_inseq - request_seq) to exactly 1 or 2 before line 800 is reached, making i equal to 0 or 1. Since strreset_result[] has exactly 2 elements (indices 0 and 1), both the truncation and the array subscript findings are false positives. No counterexample can be constructed because the guards are tight enough to exclude all dangerous values.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohl() line 800 |
| Taint snippet | i = asoc->strreset_inseq - request_seq - 1; |
| Tainted var | i |
| Truncation | line 800: 32 → 16-bit u16 |
| Sink snippet | i = asoc->strreset_inseq - request_seq - 1; |
| Possibly guarded | no |
Dismissed: The TSN guards at lines 795-796 and 799 restrict the code path to cases where request_seq is either strreset_inseq-1 or strreset_inseq-2, so the RHS of the assignment is 0 or 1. Truncation from u32 to u16 is harmless here. No counterexample can be constructed.
Finding #2 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohl() line 794 |
| Taint snippet | request_seq = ntohl(addstrm->request_seq); |
| Tainted var | i |
| Subscript | [] line 801 |
| Sink snippet | result = asoc->strreset_result[i]; |
| Possibly guarded | no |
Dismissed: The combined TSN guards ensure i is 0 or 1 before strreset_result[i] is accessed. The strreset_result array has exactly 2 elements, so this access is always in-bounds. No counterexample can be constructed.
sctp_process_strreset_inreq() — net/sctp/stream.c FP confidence=high
The function has layered validation: (1) request_seq is bounded by TSN sequence checks so i∈{0,1} before array access; (2) nums is bounded by SCTP_MAX_CHUNK_LEN check; (3) each str_p[i] is validated against stream->outcnt in the first loop before any state writes; (4) the second loop at 668 uses stream->outcnt which is a local kernel value. All flagged sinks are adequately protected.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohl() line 625 |
| Taint snippet | i = asoc->strreset_inseq - request_seq - 1; |
| Tainted var | i |
| Truncation | line 625: 32 → 16-bit u16 |
| Sink snippet | i = asoc->strreset_inseq - request_seq - 1; |
| Possibly guarded | no |
Dismissed: The TSN_lt checks at lines 620-621 constrain request_seq to [strreset_inseq-2, strreset_inseq-1], making i∈{0,1}. strreset_result[] has exactly 2 entries. No counterexample can be constructed where i≥2 passes the guards.
Finding #2 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohl() line 619 |
| Taint snippet | request_seq = ntohl(inreq->request_seq); |
| Tainted var | i |
| Subscript | [] line 626 |
| Sink snippet | result = asoc->strreset_result[i]; |
| Possibly guarded | no |
Dismissed: Same as finding #1: TSN_lt guards ensure i∈{0,1} before strreset_result[i] access. The array has 2 slots. Properly bounded.
Finding #3 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 619 |
| Taint snippet | request_seq = ntohl(inreq->request_seq); |
| Tainted var | i |
| Loop | for_loop line 646 |
| Sink snippet | for (i = 0; i < nums; i++) { |
| Possibly guarded | no |
Dismissed: nums is bounded by the check at line 643-645 against SCTP_MAX_CHUNK_LEN. The loop at 646 accesses str_p[i] which is within the received parameter buffer since nums was derived from param.p->length. The scanner incorrectly traces taint through request_seq to i; the actual loop variable is i used as index into str_p, not as a value derived from request_seq.
Finding #4 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohl() line 619 |
| Taint snippet | request_seq = ntohl(inreq->request_seq); |
| Tainted var | i |
| Subscript | [] line 647 |
| Sink snippet | if (ntohs(str_p[i]) >= stream->outcnt) { |
| Possibly guarded | no |
Dismissed: str_p[i] accesses are within buffer bounds because nums is derived from param.p->length and bounded by the SCTP_MAX_CHUNK_LEN check. ntohs(str_p[i]) is then checked against stream->outcnt, preventing OOB stream access.
Finding #5 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 619 |
| Taint snippet | request_seq = ntohl(inreq->request_seq); |
| Tainted var | i |
| Loop | for_loop line 664 |
| Sink snippet | for (i = 0; i < nums; i++) |
| Possibly guarded | yes (heuristic) |
Dismissed: Loop at line 664 uses nums with str_p[i]. Prior to this loop, the loop at 646-651 performed a full traversal validating every str_p[i] < stream->outcnt and returning early on any invalid value. This constitutes form (c) protection — prior full-traversal validation.
Finding #6 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 619 |
| Taint snippet | request_seq = ntohl(inreq->request_seq); |
| Tainted var | i |
| Pointer deref | i->state line 665 |
| Sink snippet | SCTP_SO(stream, ntohs(str_p[i]))->state = |
| Possibly guarded | yes (heuristic) |
Dismissed: SCTP_SO(stream, ntohs(str_p[i])) uses values already validated in the loop at 646-651 to be < stream->outcnt. The state write is safe within stream->out array bounds.
Finding #7 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohl() line 619 |
| Taint snippet | request_seq = ntohl(inreq->request_seq); |
| Tainted var | i |
| Subscript | [] line 665 |
| Sink snippet | SCTP_SO(stream, ntohs(str_p[i]))->state = |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #6 — str_p[i] values were validated against stream->outcnt before this point.
Finding #8 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 619 |
| Taint snippet | request_seq = ntohl(inreq->request_seq); |
| Tainted var | i |
| Loop | for_loop line 668 |
| Sink snippet | for (i = 0; i < stream->outcnt; i++) |
| Possibly guarded | yes (heuristic) |
Dismissed: Loop at line 668 uses stream->outcnt as bound and i as index — both are local kernel values, not server-supplied. stream->outcnt controls the size of stream->out. The scanner incorrectly traces taint from request_seq to this loop's bound.
Finding #9 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 619 |
| Taint snippet | request_seq = ntohl(inreq->request_seq); |
| Tainted var | i |
| Pointer deref | i->state line 669 |
| Sink snippet | SCTP_SO(stream, i)->state = SCTP_STREAM_CLOSED; |
| Possibly guarded | yes (heuristic) |
Dismissed: SCTP_SO(stream, i) where i < stream->outcnt is safe by loop invariant. stream->outcnt is a kernel-managed value, not server-supplied.
Finding #10 — Category F — cross-function via sctp_stream_outq_is_empty() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 641 |
| Taint snippet | nums = (ntohs(param.p->length) - sizeof(*inreq)) / sizeof(__u16); |
| Tainted var | nums |
| Call site | line 653 — passes nums to sctp_stream_outq_is_empty() |
| Call snippet | if (!sctp_stream_outq_is_empty(stream, nums, str_p)) { |
| Loop | for_loop line 254 |
| Sink snippet | for (i = 0; i < str_nums; i++) { |
| Possibly guarded | yes (heuristic) |
Dismissed: sctp_stream_outq_is_empty iterates str_list[i] for i<str_nums. nums is bounded by SCTP_MAX_CHUNK_LEN check (line 643-645), and the str_p buffer has at least nums elements since nums was derived from param.p->length. The loop inside sctp_stream_outq_is_empty only reads from str_list which was validated to be within the packet buffer. No OOB access possible.
sctp_process_strreset_outreq() — net/sctp/stream.c MIXED confidence=medium
The function processes server-supplied SCTP stream reset parameters. Most findings are false positives: the i=0/1 truncation at line 542 is bounded by TSN guards; the second loop at 589 is protected by the first loop's per-element validation; the cross-function findings in sctp_ulpevent_make_stream_reset_event are false positives because the allocation is sized to accommodate stream_num elements. The main genuine concern is whether nums at line 555 is bounded against the actual received parameter buffer length before str_p[] is accessed in the first loop — SCTP's param dispatch should enforce this, but it depends on upper-layer validation not shown here.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohl() line 542 |
| Taint snippet | i = asoc->strreset_inseq - request_seq - 1; |
| Tainted var | i |
| Truncation | line 542: 32 → 16-bit u16 |
| Sink snippet | i = asoc->strreset_inseq - request_seq - 1; |
| Possibly guarded | no |
Dismissed: i = strreset_inseq - request_seq - 1. The guard at lines 537-541 ensures request_seq is in [strreset_inseq-2, strreset_inseq-1], so i is either 0 or 1 after truncation to u16. No counterexample possible: the guards are tight enough that the 32-to-16 truncation cannot produce an unexpected value.
Finding #2 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohl() line 529 |
| Taint snippet | request_seq = ntohl(outreq->request_seq); |
| Tainted var | i |
| Subscript | [] line 543 |
| Sink snippet | result = asoc->strreset_result[i]; |
| Possibly guarded | no |
Dismissed: asoc->strreset_result[i] where i is 0 or 1 (proven by TSN guards). strreset_result is an array of size 2, so both indices are valid. No counterexample: i cannot exceed 1 given the guards.
Finding #3 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 529 |
| Taint snippet | request_seq = ntohl(outreq->request_seq); |
| Tainted var | i |
| Loop | for_loop line 557 |
| Sink snippet | for (i = 0; i < nums; i++) { |
| Possibly guarded | no |
Dismissed: nums is derived from ntohs(param.p->length). SCTP's upper-layer param dispatch validates that param.p->length does not exceed the received chunk buffer before calling this function, so str_p[i] accesses within nums elements remain within the packet buffer. This is protected by the SCTP parameter length validation in the dispatch path. Marking as false positive pending confirmation of upper-layer validation.
Finding #4 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohl() line 529 |
| Taint snippet | request_seq = ntohl(outreq->request_seq); |
| Tainted var | i |
| Subscript | [] line 558 |
| Sink snippet | if (ntohs(str_p[i]) >= stream->incnt) { |
| Possibly guarded | no |
Dismissed: str_p[i] where i < nums, and nums is computed from the param length field. SCTP validates param lengths at dispatch; the loop body checks ntohs(str_p[i]) >= stream->incnt to validate stream IDs. Access to str_p[i] within the param buffer should be safe if upper-layer length validation is done. The per-element check validates stream IDs are in range.
Finding #5 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 529 |
| Taint snippet | request_seq = ntohl(outreq->request_seq); |
| Tainted var | i |
| Loop | for_loop line 589 |
| Sink snippet | for (i = 0; i < nums; i++) |
| Possibly guarded | yes (heuristic) |
Dismissed: The loop at line 589 is protected by the prior full-traversal loop at lines 557-562. That loop validated every str_p[i] (same i range, same nums bound) checking each stream ID against stream->incnt. If any ID was out of range, the function jumped to 'out' before reaching line 589. So at line 589, all stream IDs in str_p[0..nums-1] are known to be < stream->incnt. SCTP_SI indexing is safe.
Finding #6 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 529 |
| Taint snippet | request_seq = ntohl(outreq->request_seq); |
| Tainted var | i |
| Pointer deref | i->mid line 590 |
| Sink snippet | SCTP_SI(stream, ntohs(str_p[i]))->mid = 0; |
| Possibly guarded | yes (heuristic) |
Dismissed: SCTP_SI(stream, ntohs(str_p[i])) where ntohs(str_p[i]) < stream->incnt is guaranteed by the prior validation loop. The mid field access is within valid stream array bounds. False positive.
Finding #7 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohl() line 529 |
| Taint snippet | request_seq = ntohl(outreq->request_seq); |
| Tainted var | i |
| Subscript | [] line 590 |
| Sink snippet | SCTP_SI(stream, ntohs(str_p[i]))->mid = 0; |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #6: prior loop validated all stream IDs against stream->incnt. The subscript ntohs(str_p[i]) is proven < stream->incnt for all i in [0, nums). False positive.
Finding #8 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 529 |
| Taint snippet | request_seq = ntohl(outreq->request_seq); |
| Tainted var | i |
| Loop | for_loop line 592 |
| Sink snippet | for (i = 0; i < stream->incnt; i++) |
| Possibly guarded | yes (heuristic) |
Dismissed: This loop uses stream->incnt as the bound, not a server-supplied value. stream->incnt is a kernel-internal association parameter, not server-controlled in this context. The loop iterates over all inbound streams which the kernel itself allocated. False positive due to taint propagation confusion.
Finding #9 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 529 |
| Taint snippet | request_seq = ntohl(outreq->request_seq); |
| Tainted var | i |
| Pointer deref | i->mid line 593 |
| Sink snippet | SCTP_SI(stream, i)->mid = 0; |
| Possibly guarded | yes (heuristic) |
Dismissed: SCTP_SI(stream, i) where i < stream->incnt (loop bound). stream->incnt is a kernel-internal value representing the allocated stream array size. Accessing stream index i < incnt is safe by construction. False positive.
Finding #10 — Category E — cross-function via sctp_ulpevent_make_stream_reset_event() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 555 |
| Taint snippet | nums = (ntohs(param.p->length) - sizeof(*outreq)) / sizeof(__u16); |
| Tainted var | nums |
| Call site | line 597 — passes nums to sctp_ulpevent_make_stream_reset_event() |
| Call snippet | *evp = sctp_ulpevent_make_stream_reset_event(asoc, |
| Pointer deref | nums-> line 905 |
| Sink snippet | sreset->strreset_type = SCTP_STREAM_RESET_EVENT; |
| Possibly guarded | no |
Dismissed: sreset is obtained from skb_put(skb, length) where length = sizeof(struct sctp_stream_reset_event) + 2*stream_num. The struct fields being accessed (strreset_type, strreset_flags, etc.) are within the fixed-size prefix of the struct, well within the allocated length. The taint propagation is spurious — nums affects length which determines the skb allocation, but the fixed struct fields are always within bounds. False positive.
Finding #11 — Category E — cross-function via sctp_ulpevent_make_stream_reset_event() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 555 |
| Taint snippet | nums = (ntohs(param.p->length) - sizeof(*outreq)) / sizeof(__u16); |
| Tainted var | nums |
| Call site | line 597 — passes nums to sctp_ulpevent_make_stream_reset_event() |
| Call snippet | *evp = sctp_ulpevent_make_stream_reset_event(asoc, |
| Pointer deref | nums-> line 906 |
| Sink snippet | sreset->strreset_flags = flags; |
| Possibly guarded | no |
Dismissed: Same as #10. sreset->strreset_flags is a fixed field in the struct, within the skb_put allocation. False positive from taint propagation.
Finding #12 — Category E — cross-function via sctp_ulpevent_make_stream_reset_event() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 555 |
| Taint snippet | nums = (ntohs(param.p->length) - sizeof(*outreq)) / sizeof(__u16); |
| Tainted var | nums |
| Call site | line 597 — passes nums to sctp_ulpevent_make_stream_reset_event() |
| Call snippet | *evp = sctp_ulpevent_make_stream_reset_event(asoc, |
| Pointer deref | nums-> line 907 |
| Sink snippet | sreset->strreset_length = length; |
| Possibly guarded | no |
Dismissed: Same as #10. sreset->strreset_length is within the fixed struct prefix, always within the skb_put allocation. False positive.
Finding #13 — Category E — cross-function via sctp_ulpevent_make_stream_reset_event() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 555 |
| Taint snippet | nums = (ntohs(param.p->length) - sizeof(*outreq)) / sizeof(__u16); |
| Tainted var | nums |
| Call site | line 597 — passes nums to sctp_ulpevent_make_stream_reset_event() |
| Call snippet | *evp = sctp_ulpevent_make_stream_reset_event(asoc, |
| Pointer deref | nums-> line 909 |
| Sink snippet | sreset->strreset_assoc_id = sctp_assoc2id(asoc); |
| Possibly guarded | no |
Dismissed: Same as #10. sreset->strreset_assoc_id is within the fixed struct prefix. False positive.
Finding #14 — Category F — cross-function via sctp_ulpevent_make_stream_reset_event() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 555 |
| Taint snippet | nums = (ntohs(param.p->length) - sizeof(*outreq)) / sizeof(__u16); |
| Tainted var | nums |
| Call site | line 597 — passes nums to sctp_ulpevent_make_stream_reset_event() |
| Call snippet | *evp = sctp_ulpevent_make_stream_reset_event(asoc, |
| Loop | for_loop line 911 |
| Sink snippet | for (i = 0; i < stream_num; i++) |
| Possibly guarded | no |
Dismissed: The loop in sctp_ulpevent_make_stream_reset_event iterates stream_num times writing to sreset->strreset_stream_list[i]. The skb allocation is length = sizeof(struct) + 2*stream_num bytes, and strreset_stream_list is a flexible array at the end of the struct. The loop writes exactly 2*stream_num bytes into the allocated space — no OOB. stream_num (__u16) is at most 32767, so 2*stream_num fits in int. False positive.
Finding #15 — Category E — cross-function via sctp_ulpevent_make_stream_reset_event() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 555 |
| Taint snippet | nums = (ntohs(param.p->length) - sizeof(*outreq)) / sizeof(__u16); |
| Tainted var | nums |
| Call site | line 597 — passes nums to sctp_ulpevent_make_stream_reset_event() |
| Call snippet | *evp = sctp_ulpevent_make_stream_reset_event(asoc, |
| Pointer deref | nums-> line 912 |
| Sink snippet | sreset->strreset_stream_list[i] = ntohs(stream_list[i]); |
| Possibly guarded | no |
Dismissed: sreset->strreset_stream_list[i] for i < stream_num, within the skb_put allocated region of size sizeof(struct) + 2*stream_num. Access is within bounds by construction of the allocation. False positive.
sctp_process_strreset_resp() — net/sctp/stream.c FP confidence=high
The flagged values (outreq->param_hdr.length, addstrm->number_of_streams) come from locally-built kernel request structs stored in asoc->strreset_chunk, not from server-supplied response data. The resp (server-supplied) is only used for result/response_seq. The req pointer retrieved via sctp_chunk_lookup_strreset_param points to the kernel's own previously-sent request chunk. All tainted values are internally controlled, making all findings false positives.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 951 |
| Taint snippet | nums = (ntohs(outreq->param_hdr.length) - sizeof(*outreq)) / |
| Tainted var | nums |
| Loop | for_loop line 957 |
| Sink snippet | for (i = 0; i < nums; i++) { |
| Possibly guarded | no |
Dismissed: outreq is cast from req, which was retrieved from asoc->strreset_chunk — a locally-built kernel request, not a server response. nums derived from outreq->param_hdr.length is kernel-controlled. str_p points into the locally-built request. No server-supplied data controls this loop.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 1049 |
| Taint snippet | nums = ntohs(addstrm->number_of_streams); |
| Tainted var | i |
| Pointer deref | i->state line 1054 |
| Sink snippet | SCTP_SO(stream, i)->state = SCTP_STREAM_OPEN; |
| Possibly guarded | no |
Dismissed: addstrm is cast from req retrieved from asoc->strreset_chunk — a locally-built kernel request. addstrm->number_of_streams was set by the kernel when building the ADD_OUT_STREAMS request. SCTP_SO(stream, i) uses loop variable i bounded by stream->outcnt, not by nums directly.
Finding #3 — Category E — cross-function via sctp_ulpevent_make_stream_reset_event() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 951 |
| Taint snippet | nums = (ntohs(outreq->param_hdr.length) - sizeof(*outreq)) / |
| Tainted var | nums |
| Call site | line 976 — passes nums to sctp_ulpevent_make_stream_reset_event() |
| Call snippet | *evp = sctp_ulpevent_make_stream_reset_event(asoc, flags, |
| Pointer deref | nums-> line 905 |
| Sink snippet | sreset->strreset_type = SCTP_STREAM_RESET_EVENT; |
| Possibly guarded | no |
Dismissed: nums is from locally-built outreq. In sctp_ulpevent_make_stream_reset_event(), sreset is allocated via skb_put with length = sizeof(sctp_stream_reset_event) + 2*stream_num, so sreset access at line 905 is within the newly allocated buffer. False positive — scanner misidentifies the taint source and the allocation pattern.
Finding #4 — Category E — cross-function via sctp_ulpevent_make_stream_reset_event() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 951 |
| Taint snippet | nums = (ntohs(outreq->param_hdr.length) - sizeof(*outreq)) / |
| Tainted var | nums |
| Call site | line 976 — passes nums to sctp_ulpevent_make_stream_reset_event() |
| Call snippet | *evp = sctp_ulpevent_make_stream_reset_event(asoc, flags, |
| Pointer deref | nums-> line 906 |
| Sink snippet | sreset->strreset_flags = flags; |
| Possibly guarded | no |
Dismissed: Same as finding #3. sreset->strreset_flags write at line 906 is within the skb_put-allocated buffer sized to accommodate stream_num. False positive.
Finding #5 — Category E — cross-function via sctp_ulpevent_make_stream_reset_event() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 951 |
| Taint snippet | nums = (ntohs(outreq->param_hdr.length) - sizeof(*outreq)) / |
| Tainted var | nums |
| Call site | line 976 — passes nums to sctp_ulpevent_make_stream_reset_event() |
| Call snippet | *evp = sctp_ulpevent_make_stream_reset_event(asoc, flags, |
| Pointer deref | nums-> line 907 |
| Sink snippet | sreset->strreset_length = length; |
| Possibly guarded | no |
Dismissed: Same as finding #3. sreset->strreset_length write at line 907 is within the skb_put-allocated buffer. False positive.
Finding #6 — Category E — cross-function via sctp_ulpevent_make_stream_reset_event() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 951 |
| Taint snippet | nums = (ntohs(outreq->param_hdr.length) - sizeof(*outreq)) / |
| Tainted var | nums |
| Call site | line 976 — passes nums to sctp_ulpevent_make_stream_reset_event() |
| Call snippet | *evp = sctp_ulpevent_make_stream_reset_event(asoc, flags, |
| Pointer deref | nums-> line 909 |
| Sink snippet | sreset->strreset_assoc_id = sctp_assoc2id(asoc); |
| Possibly guarded | no |
Dismissed: Same as finding #3. sreset->strreset_assoc_id write at line 909 is within the skb_put-allocated buffer. False positive.
Finding #7 — Category F — cross-function via sctp_ulpevent_make_stream_reset_event() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 951 |
| Taint snippet | nums = (ntohs(outreq->param_hdr.length) - sizeof(*outreq)) / |
| Tainted var | nums |
| Call site | line 976 — passes nums to sctp_ulpevent_make_stream_reset_event() |
| Call snippet | *evp = sctp_ulpevent_make_stream_reset_event(asoc, flags, |
| Loop | for_loop line 911 |
| Sink snippet | for (i = 0; i < stream_num; i++) |
| Possibly guarded | no |
Dismissed: nums is locally controlled (from kernel-built outreq). The loop in sctp_ulpevent_make_stream_reset_event() at line 911 iterates over stream_list (locally-built str_p) with stream_num (locally-derived nums) as bound. The sreset->strreset_stream_list array is sized to 2*stream_num in the allocation. False positive.
Finding #8 — Category E — cross-function via sctp_ulpevent_make_stream_reset_event() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 951 |
| Taint snippet | nums = (ntohs(outreq->param_hdr.length) - sizeof(*outreq)) / |
| Tainted var | nums |
| Call site | line 976 — passes nums to sctp_ulpevent_make_stream_reset_event() |
| Call snippet | *evp = sctp_ulpevent_make_stream_reset_event(asoc, flags, |
| Pointer deref | nums-> line 912 |
| Sink snippet | sreset->strreset_stream_list[i] = ntohs(stream_list[i]); |
| Possibly guarded | no |
Dismissed: Same as finding #7. sreset->strreset_stream_list[i] access is within the skb_put buffer sized for stream_num entries. stream_list[i] reads from locally-built str_p. False positive.
Finding #9 — Category F — cross-function via sctp_stream_outq_migrate() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 1049 |
| Taint snippet | nums = ntohs(addstrm->number_of_streams); |
| Tainted var | number |
| Call site | line 1058 — passes number to sctp_stream_outq_migrate() |
| Call snippet | sctp_stream_outq_migrate(stream, NULL, number); |
| Loop | for_loop line 85 |
| Sink snippet | for (i = 0; i < outcnt; i++) { |
| Possibly guarded | no |
Dismissed: addstrm->number_of_streams is from a locally-built kernel request. number = stream->outcnt - nums is a kernel-computed value. sctp_stream_outq_migrate() with outcnt=number only accesses stream->out entries up to min(stream->outcnt, outcnt). False positive.
sctp_process_strreset_tsnreq() — net/sctp/stream.c FP confidence=high
The scanner propagates taint from 'request_seq' (server-supplied) to 'i' at line 704, then incorrectly continues propagating that taint to uses of 'i' in the loops at lines 764-769. However, 'i' is a reused local variable — in the loops, it is initialized to 0 and incremented as a plain counter bounded by kernel-internal values (stream->outcnt, stream->incnt), not by any server-supplied value. Findings #1 and #2 relate to line 704-705 where i is indeed derived from server data, but the sequence number guards at lines 699-703 mathematically constrain i to {0, 1}, matching the 2-element strreset_result array. All findings are false positives.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | ntohl() line 704 |
| Taint snippet | i = asoc->strreset_inseq - request_seq - 1; |
| Tainted var | i |
| Truncation | line 704: 32 → 16-bit u16 |
| Sink snippet | i = asoc->strreset_inseq - request_seq - 1; |
| Possibly guarded | no |
Dismissed: The guards at lines 699-703 ensure request_seq is within [strreset_inseq-2, strreset_inseq-1], so i = strreset_inseq - request_seq - 1 is in {0, 1}. No counterexample: any value of request_seq passing both guards yields i in {0,1}, within the 2-element array. The truncation from 32-bit to 16-bit is also safe since the value is at most 1.
Finding #2 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohl() line 698 |
| Taint snippet | request_seq = ntohl(tsnreq->request_seq); |
| Tainted var | i |
| Subscript | [] line 705 |
| Sink snippet | result = asoc->strreset_result[i]; |
| Possibly guarded | no |
Dismissed: Same analysis as #1. The guards mathematically constrain i to {0,1}. strreset_result[] has 2 elements indexed by 0 and 1. No out-of-bounds access is possible.
Finding #3 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 698 |
| Taint snippet | request_seq = ntohl(tsnreq->request_seq); |
| Tainted var | i |
| Loop | for_loop line 764 |
| Sink snippet | for (i = 0; i < stream->outcnt; i++) { |
| Possibly guarded | no |
Dismissed: False positive due to taint variable name reuse. In the loop at line 764, 'i' is a fresh loop counter starting at 0, bounded by stream->outcnt which is a kernel-internal property of the association, not derived from server-supplied request_seq. The scanner incorrectly propagated taint from the earlier use of 'i' at line 704.
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 698 |
| Taint snippet | request_seq = ntohl(tsnreq->request_seq); |
| Tainted var | i |
| Pointer deref | i->mid line 765 |
| Sink snippet | SCTP_SO(stream, i)->mid = 0; |
| Possibly guarded | no |
Dismissed: Same as #3. SCTP_SO(stream, i) uses loop counter i in [0, stream->outcnt), which is a kernel-maintained value. Not server-controlled. False positive.
Finding #5 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 698 |
| Taint snippet | request_seq = ntohl(tsnreq->request_seq); |
| Tainted var | i |
| Pointer deref | i->mid_uo line 766 |
| Sink snippet | SCTP_SO(stream, i)->mid_uo = 0; |
| Possibly guarded | no |
Dismissed: Same as #3 and #4. mid_uo field write is on stream->out[i] where i is a loop counter bounded by kernel-internal outcnt. False positive.
Finding #6 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 698 |
| Taint snippet | request_seq = ntohl(tsnreq->request_seq); |
| Tainted var | i |
| Loop | for_loop line 768 |
| Sink snippet | for (i = 0; i < stream->incnt; i++) |
| Possibly guarded | no |
Dismissed: Same reasoning as #3. The loop at line 768 uses i as a fresh counter from 0 to stream->incnt (kernel-internal). Not server-derived. False positive.
Finding #7 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohl() line 698 |
| Taint snippet | request_seq = ntohl(tsnreq->request_seq); |
| Tainted var | i |
| Pointer deref | i->mid line 769 |
| Sink snippet | SCTP_SI(stream, i)->mid = 0; |
| Possibly guarded | no |
Dismissed: Same as #6. SCTP_SI(stream, i) uses loop counter bounded by kernel-internal incnt. False positive.
smc_clc_msg_hdr_valid() — net/smc/smc_clc.c FP confidence=medium
The trl pointer is derived from server-supplied ntohs(hdr.length), but two layers of protection exist: (1) the call site in smc_clc_wait_msg() sets check_trl=false when datlen>buflen, ensuring trl computation only happens when hdr.length<=buflen; (2) smc_clc_msg_acc_conf_valid() is called before trl is computed and (by analogy with smc_clc_msg_decl_valid) validates hdr.length against known struct sizes, preventing underflow. The analysis is medium-confidence because smc_clc_msg_acc_conf_valid source is not provided.
Finding #1 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | ntohs() line 492 |
| Taint snippet | trl = (struct smc_clc_msg_trail *) |
| Tainted var | trl |
| Sink | memcmp() line 505 (arg 0, role=pointer) |
| Sink snippet | memcmp(trl->eyecatcher, SMC_EYECATCHER, sizeof(SMC_EYECATCHER)) && |
| Possibly guarded | no |
Dismissed: smc_clc_msg_acc_conf_valid() validates hdr.length before trl is computed; call site ensures datlen<=buflen when check_trl=true. No counterexample possible if acc_conf_valid checks minimum length.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 492 |
| Taint snippet | trl = (struct smc_clc_msg_trail *) |
| Tainted var | trl |
| Pointer deref | trl->eyecatcher line 505 |
| Sink snippet | memcmp(trl->eyecatcher, SMC_EYECATCHER, sizeof(SMC_EYECATCHER)) && |
| Possibly guarded | no |
Dismissed: Same reasoning as finding #1 — trl dereference is protected by upstream validator and call-site datlen<=buflen guard.
Finding #3 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | ntohs() line 492 |
| Taint snippet | trl = (struct smc_clc_msg_trail *) |
| Tainted var | trl |
| Sink | memcmp() line 506 (arg 0, role=pointer) |
| Sink snippet | memcmp(trl->eyecatcher, SMCD_EYECATCHER, sizeof(SMCD_EYECATCHER))) |
| Possibly guarded | no |
Dismissed: Same reasoning as finding #1 — second memcmp on line 506 is guarded by the same validation chain.
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | ntohs() line 492 |
| Taint snippet | trl = (struct smc_clc_msg_trail *) |
| Tainted var | trl |
| Pointer deref | trl->eyecatcher line 506 |
| Sink snippet | memcmp(trl->eyecatcher, SMCD_EYECATCHER, sizeof(SMCD_EYECATCHER))) |
| Possibly guarded | no |
Dismissed: Same reasoning as finding #2 — second trl->eyecatcher dereference on line 506 is covered by the same validation chain.
smc_rtoken_set() — net/smc/smc_core.c FP confidence=high
The static analyzer propagates taint from ntohl(nw_rkey_known) through the return value of smc_rtoken_find_by_link(). However, that function uses the network-supplied key only as a search key; its return value is a local loop index i in [0, SMC_RMBS_PER_LGR_MAX), or -ENOENT. The -ENOENT case is explicitly handled with an early return, so rtok_idx at the sink is always in [0, SMC_RMBS_PER_LGR_MAX). No counterexample can be constructed. Both findings are false positives.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohl() line 2642 |
| Taint snippet | rtok_idx = smc_rtoken_find_by_link(lgr, link_idx, ntohl(nw_rkey_known)); |
| Tainted var | rtok_idx |
| Subscript | [] line 2645 |
| Sink snippet | lgr->rtokens[rtok_idx][link_idx_new].rkey = ntohl(nw_rkey); |
| Possibly guarded | yes (heuristic) |
Dismissed: rtok_idx is not the server-supplied value itself but the loop index from smc_rtoken_find_by_link(), bounded to [0, SMC_RMBS_PER_LGR_MAX). The == -ENOENT check handles the only error path. Could not construct a counterexample — the bounds are tight.
Finding #2 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohl() line 2642 |
| Taint snippet | rtok_idx = smc_rtoken_find_by_link(lgr, link_idx, ntohl(nw_rkey_known)); |
| Tainted var | rtok_idx |
| Subscript | [] line 2646 |
| Sink snippet | lgr->rtokens[rtok_idx][link_idx_new].dma_addr = be64_to_cpu(nw_vaddr); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same reasoning as finding #1. rtok_idx is a loop-bounded internal index, not a direct alias of the network-supplied rkey. The guard is sufficient; no counterexample exists.
put_user_ifreq() — net/socket.c FP confidence=high
The 'size' variable in put_user_ifreq() is entirely kernel-controlled. It is initialised to sizeof(struct ifreq) and can only be changed to sizeof(struct compat_ifreq) based on the kernel's own in_compat_syscall() check. Neither value is user-supplied or server-supplied — both are compile-time constants determined by the kernel ABI. The source buffer 'ifr' is a kernel pointer passed in by the caller. The copy_to_user() destination 'arg' is user-space, but the size argument is not user-controlled in any way. The scanner's taint chain appears to be a false positive arising from treating the return value or arguments of copy_to_user() itself as tainted, which is incorrect here.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 3459 |
| Taint snippet | if (copy_to_user(arg, ifr, size)) |
| Tainted var | size |
| Unvalidated size | copy_to_user() arg 2 line 3459 — size size |
| Sink snippet | if (copy_to_user(arg, ifr, size)) |
| Possibly guarded | no |
Dismissed: The 'size' variable is set exclusively from sizeof() expressions on kernel-defined struct types (struct ifreq or struct compat_ifreq), controlled by in_compat_syscall(). These are compile-time constants — not user-supplied or server-supplied values. No counterexample can be constructed because the value of 'size' cannot be influenced by user input. This is a clear false positive from the static analyser treating the size in copy_to_user() as tainted without tracking that it originates from kernel-internal sizeof() computations.
rpc_pipe_generic_upcall() — net/sunrpc/rpc_pipe.c FP confidence=high
The scanner misidentifies the taint source and sink. 'mlen' is computed as min(msg->len - msg->copied, buflen), where buflen is the kernel-supplied destination buffer length passed in by the VFS read path. The copy_to_user call uses mlen as the size, which is already bounded by both the message's remaining data (msg->len - msg->copied) and the caller-supplied buflen. The 'taint source' the scanner cites is the return value of copy_to_user (the number of bytes NOT copied), which is then used to adjust mlen downward — this is standard copy_to_user residue handling, not a dangerous size derived from user or server input. There is no OOB risk here.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 111 |
| Taint snippet | left = copy_to_user(dst, data, mlen); |
| Tainted var | mlen |
| Unvalidated size | copy_to_user() arg 2 line 111 — size mlen |
| Sink snippet | left = copy_to_user(dst, data, mlen); |
| Possibly guarded | no |
Dismissed: The scanner incorrectly treats the return value of copy_to_user() (the 'left' residue count) as a taint source and then flags the same copy_to_user() call as both source and sink. In reality, mlen = min(msg->len - msg->copied, buflen) is bounded above by both the message's remaining length and the caller-provided buffer length. The subsequent 'left = copy_to_user(dst, data, mlen)' is entirely safe. The 'mlen -= left' line adjusts for bytes not transferred, and the result is used only to update msg->copied and the return value — no second copy occurs. No counterexample can be constructed where mlen exceeds buflen, because min() enforces that invariant. This is a false positive.
tipc_nl_compat_name_table_dump_header() — net/tipc/netlink_compat.c FP confidence=high
The depth value is server-supplied (from ntohl of a network field) but is explicitly clamped to a maximum of 4 before the loop. The header array has exactly 4 elements (indices 0-3). With depth clamped to [0,4] and the loop running for i in [0, depth), the maximum index accessed is 3, which is always within bounds. No counterexample can be constructed: any depth value from ntohl() that exceeds 4 is reduced to exactly 4, and the loop accesses header[0] through header[3] at most — all valid indices into the 4-element array.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 884 |
| Taint snippet | depth = ntohl(ntq->depth); |
| Tainted var | depth |
| Loop | for_loop line 888 |
| Sink snippet | for (i = 0; i < depth; i++) |
| Possibly guarded | no |
Dismissed: The guard 'if (depth > 4) depth = 4;' at line 886-887 clamps the loop bound to exactly the size of the header array (4 elements). No counterexample exists: depth is always in [0,4] before the loop, so i is always in [0,3], and header[i] is always a valid access. The scanner's 'possibly guarded: no' flag appears to have missed this explicit clamp. This is a false positive.
tipc_rcv() — net/tipc/node.c FP confidence=high
tipc_rcv() has solid validation discipline: tipc_msg_validate() is called before header field access, node lookup failures are checked, and bearer_id is derived from a local struct field. The flagged finding misidentifies tipc_node_find() as a dangerous sink when it is actually a lookup function that bounds its hash subscript via power-of-2 masking (tipc_hashfn uses & (NODE_HTABLE_SIZE-1)), making OOB array access impossible regardless of the input value.
Finding #1 — Category C — cross-function via tipc_node_find() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohl() line 2105 |
| Taint snippet | n = tipc_node_find(net, ntohl(ehdr->addr)); |
| Tainted var | ntohl(ehdr->addr) |
| Call site | line 2105 — passes ntohl(ehdr->addr) to tipc_node_find() |
| Call snippet | n = tipc_node_find(net, ntohl(ehdr->addr)); |
| Subscript (in callee) | [] line 337 |
| Sink snippet | hlist_for_each_entry_rcu(node, &tn->node_htable[thash], hash) { |
| Possibly guarded | no |
Dismissed: tipc_node_find() is a hash-table lookup function, not a vulnerable sink. tipc_hashfn() computes addr & (NODE_HTABLE_SIZE-1), bounding the subscript to [0, NODE_HTABLE_SIZE-1] regardless of the 32-bit input value. No counterexample exists: for any 32-bit ntohl(ehdr->addr), the AND mask guarantees the subscript stays within the statically-allocated node_htable array. The scanner flagged the hash table dereference inside the lookup helper without accounting for the masking arithmetic.
virtio_transport_build_skb() — net/vmw_vsock/virtio_transport_common.c FP confidence=high
The scanner incorrectly propagated taint from payload_len (le32_to_cpu result) to hdr. The hdr pointer is obtained via skb_put(skb, sizeof(*hdr)) on a freshly kernel-allocated skb, not via any offset arithmetic using payload_len. The only legitimate concern would be a potential integer overflow in the alloc_skb size argument (sizeof(*hdr) + sizeof(*pkt_hdr) + payload_len) if payload_len is very large, but that is not what was flagged. All flagged accesses through hdr are into locally-allocated memory of exactly sizeof(*hdr) bytes, making all findings false positives.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 132 |
| Taint snippet | payload_len = le32_to_cpu(pkt_hdr->len); |
| Tainted var | hdr |
| Pointer deref | hdr->src_cid line 142 |
| Sink snippet | hdr->src_cid = pkt_hdr->src_cid; |
| Possibly guarded | no |
Dismissed: hdr is from skb_put(skb, sizeof(*hdr)) on a newly allocated skb, not derived from payload_len. The taint on payload_len does not propagate to hdr's base address. False positive due to incorrect taint propagation through alloc_skb/skb_put.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 132 |
| Taint snippet | payload_len = le32_to_cpu(pkt_hdr->len); |
| Tainted var | hdr |
| Pointer deref | hdr->src_port line 143 |
| Sink snippet | hdr->src_port = pkt_hdr->src_port; |
| Possibly guarded | no |
Dismissed: Same as #1: hdr is a locally-allocated pointer, not derived from server-supplied offset arithmetic.
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 132 |
| Taint snippet | payload_len = le32_to_cpu(pkt_hdr->len); |
| Tainted var | hdr |
| Pointer deref | hdr->dst_cid line 144 |
| Sink snippet | hdr->dst_cid = pkt_hdr->dst_cid; |
| Possibly guarded | no |
Dismissed: Same as #1: hdr is a locally-allocated pointer, not derived from server-supplied offset arithmetic.
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 132 |
| Taint snippet | payload_len = le32_to_cpu(pkt_hdr->len); |
| Tainted var | hdr |
| Pointer deref | hdr->dst_port line 145 |
| Sink snippet | hdr->dst_port = pkt_hdr->dst_port; |
| Possibly guarded | no |
Dismissed: Same as #1: hdr is a locally-allocated pointer, not derived from server-supplied offset arithmetic.
Finding #5 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 132 |
| Taint snippet | payload_len = le32_to_cpu(pkt_hdr->len); |
| Tainted var | hdr |
| Pointer deref | hdr->transport line 147 |
| Sink snippet | hdr->transport = cpu_to_le16(AF_VSOCK_TRANSPORT_VIRTIO); |
| Possibly guarded | no |
Dismissed: Same as #1: hdr is a locally-allocated pointer, not derived from server-supplied offset arithmetic.
Finding #6 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 132 |
| Taint snippet | payload_len = le32_to_cpu(pkt_hdr->len); |
| Tainted var | hdr |
| Pointer deref | hdr->len line 148 |
| Sink snippet | hdr->len = cpu_to_le16(sizeof(*pkt_hdr)); |
| Possibly guarded | no |
Dismissed: Same as #1: hdr is a locally-allocated pointer, not derived from server-supplied offset arithmetic.
Finding #7 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | le32_to_cpu() line 132 |
| Taint snippet | payload_len = le32_to_cpu(pkt_hdr->len); |
| Tainted var | hdr |
| Sink | memset() line 149 (arg 2, role=size) |
| Sink snippet | memset(hdr->reserved, 0, sizeof(hdr->reserved)); |
| Possibly guarded | no |
Dismissed: The size argument to memset is sizeof(hdr->reserved), a compile-time constant, not payload_len. The destination hdr->reserved is within the locally-allocated hdr struct. False positive.
Finding #8 — Category A — false positive
| Category | Cat A — server offset → pointer → memory op |
|---|---|
| Taint source | le32_to_cpu() line 132 |
| Taint snippet | payload_len = le32_to_cpu(pkt_hdr->len); |
| Tainted var | hdr |
| Sink | memset() line 149 (arg 0, role=pointer) |
| Sink snippet | memset(hdr->reserved, 0, sizeof(hdr->reserved)); |
| Possibly guarded | no |
Dismissed: hdr->reserved is a field within the locally-allocated af_vsockmon_hdr struct. No server-supplied offset arithmetic involved in deriving this pointer.
Finding #9 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 132 |
| Taint snippet | payload_len = le32_to_cpu(pkt_hdr->len); |
| Tainted var | hdr |
| Pointer deref | hdr->reserved line 149 |
| Sink snippet | memset(hdr->reserved, 0, sizeof(hdr->reserved)); |
| Possibly guarded | no |
Dismissed: Same as #1: hdr is a locally-allocated pointer, not derived from server-supplied offset arithmetic.
Finding #10 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 132 |
| Taint snippet | payload_len = le32_to_cpu(pkt_hdr->len); |
| Tainted var | hdr |
| Pointer deref | hdr->op line 154 |
| Sink snippet | hdr->op = cpu_to_le16(AF_VSOCK_OP_CONNECT); |
| Possibly guarded | no |
Dismissed: Same as #1: hdr is a locally-allocated pointer, not derived from server-supplied offset arithmetic.
Finding #11 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 132 |
| Taint snippet | payload_len = le32_to_cpu(pkt_hdr->len); |
| Tainted var | hdr |
| Pointer deref | hdr->op line 158 |
| Sink snippet | hdr->op = cpu_to_le16(AF_VSOCK_OP_DISCONNECT); |
| Possibly guarded | no |
Dismissed: Same as #1: hdr is a locally-allocated pointer, not derived from server-supplied offset arithmetic.
Finding #12 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 132 |
| Taint snippet | payload_len = le32_to_cpu(pkt_hdr->len); |
| Tainted var | hdr |
| Pointer deref | hdr->op line 161 |
| Sink snippet | hdr->op = cpu_to_le16(AF_VSOCK_OP_PAYLOAD); |
| Possibly guarded | no |
Dismissed: Same as #1: hdr is a locally-allocated pointer, not derived from server-supplied offset arithmetic.
Finding #13 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 132 |
| Taint snippet | payload_len = le32_to_cpu(pkt_hdr->len); |
| Tainted var | hdr |
| Pointer deref | hdr->op line 165 |
| Sink snippet | hdr->op = cpu_to_le16(AF_VSOCK_OP_CONTROL); |
| Possibly guarded | no |
Dismissed: Same as #1: hdr is a locally-allocated pointer, not derived from server-supplied offset arithmetic.
Finding #14 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | le32_to_cpu() line 132 |
| Taint snippet | payload_len = le32_to_cpu(pkt_hdr->len); |
| Tainted var | hdr |
| Pointer deref | hdr->op line 168 |
| Sink snippet | hdr->op = cpu_to_le16(AF_VSOCK_OP_UNKNOWN); |
| Possibly guarded | no |
Dismissed: Same as #1: hdr is a locally-allocated pointer, not derived from server-supplied offset arithmetic.
nl80211_send_iftype_data() — net/wireless/nl80211.c FP confidence=high
The data in question (eht_cap->eht_ppe_thres) comes from driver-registered ieee80211_sband_iftype_data structures, which represent the LOCAL device's own capabilities set by the kernel wireless driver at registration time — not from network packets or server responses. The get_unaligned_le16() call is on locally-initialized driver data. Additionally, ieee80211_eht_ppe_size() itself returns u8, meaning it already constrains the output to 8 bits before assignment to ppe_thresh_size.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | get_unaligned_le16() line 2189 |
| Taint snippet | ppe_thresh_size = |
| Tainted var | ppe_thresh_size |
| Truncation | line 2189: 16 → 8-bit u8 |
| Sink snippet | ppe_thresh_size = |
| Possibly guarded | no |
Dismissed: The eht_cap data originates from struct ieee80211_sband_iftype_data registered by the local wireless driver (sband->iftype_data), not from any remote server or network packet. get_unaligned_le16() is being called on locally-initialized driver capability data. Furthermore, ieee80211_eht_ppe_size() is declared to return u8, so its result is already bounded to 8 bits before the assignment to ppe_thresh_size. No counterexample is possible because the function return type itself prevents values > 255. This is a false positive: the scanner incorrectly treated driver-registered capability data as server-supplied, and the taint path through a u8-returning function is inherently bounded.
__regdb_query_wmm() — net/wireless/reg.c BUG confidence=high
The function reads from a firmware-supplied binary blob (regdb) and uses embedded offsets (coll_ptr, rules_ptr entries) to compute struct pointers without any bounds validation against the known firmware buffer size. Both coll and rule pointers are derived from firmware-controlled offset arithmetic with no check that offset+sizeof(*struct) <= db_size before dereferencing. The cross-function findings in set_wmm_rule are likely false positives because valid_wmm() is called before the flagged accesses.
Finding #1 — Category F — BUG oob_read
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | be16_to_cpu() line 871 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | coll |
| Loop | for_loop line 875 |
| Sink snippet | for (i = 0; i < coll->n_rules; i++) { |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in __regdb_query_wmm when firmware blob contains a crafted coll_ptr yielding n_rules that causes the loop to iterate beyond the buffer
Fix: Validate that ptr + sizeof(struct fwdb_collection) <= db_size before forming coll pointer. Additionally validate coll->n_rules such that the entire rules array fits within the buffer.
CVE pattern: Firmware blob offset OOB — similar pattern to CVE-2019-17666 style firmware parsing bugs
Finding #2 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 871 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | coll |
| Pointer deref | coll->n_rules line 875 |
| Sink snippet | for (i = 0; i < coll->n_rules; i++) { |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in __regdb_query_wmm reading coll->n_rules from out-of-bounds firmware offset
Fix: Before casting (u8*)db + ptr to struct fwdb_collection*, verify ptr + sizeof(struct fwdb_collection) <= db_size.
CVE pattern: Firmware offset OOB read
Finding #3 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 871 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | coll |
| Pointer deref | coll->len line 876 |
| Sink snippet | __be16 *rules_ptr = (void *)((u8 *)coll + ALIGN(coll->len, 2)); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds reading coll->len from unvalidated firmware offset in __regdb_query_wmm
Fix: Verify ptr + sizeof(struct fwdb_collection) <= db_size before dereferencing coll. Also validate ALIGN(coll->len, 2) + n_rules * sizeof(__be16) fits within db bounds before forming rules_ptr.
CVE pattern: Firmware offset OOB read
Finding #4 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 877 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Pointer deref | rule->len line 880 |
| Sink snippet | if (rule->len < offsetofend(struct fwdb_rule, wmm_ptr)) |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds reading rule->len from crafted rules_ptr entry in __regdb_query_wmm
Fix: Validate rule_ptr + sizeof(struct fwdb_rule) <= db_size before casting to struct fwdb_rule*. The rule->len check on line 880 is itself the dangerous dereference.
CVE pattern: Firmware offset OOB read
Finding #5 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 877 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Pointer deref | rule->start line 883 |
| Sink snippet | if (freq >= KHZ_TO_MHZ(be32_to_cpu(rule->start)) && |
| Possibly guarded | yes (heuristic) |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds reading rule->start from out-of-bounds firmware offset
Fix: Validate rule_ptr + sizeof(struct fwdb_rule) <= db_size before dereferencing rule.
CVE pattern: Firmware offset OOB read
Finding #6 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 877 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Pointer deref | rule->end line 884 |
| Sink snippet | freq <= KHZ_TO_MHZ(be32_to_cpu(rule->end))) { |
| Possibly guarded | yes (heuristic) |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds reading rule->end from out-of-bounds firmware offset
Fix: Validate rule_ptr + sizeof(struct fwdb_rule) <= db_size before dereferencing rule.
CVE pattern: Firmware offset OOB read
Finding #7 — Category E — cross-function via set_wmm_rule() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 877 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Call site | line 885 — passes rule to set_wmm_rule() |
| Call snippet | set_wmm_rule(db, country, rule, rrule); |
| Pointer deref | rule-> line 853 |
| Sink snippet | ecw2cw((wmm->client[i].ecw & 0xf0) >> 4); |
| Possibly guarded | yes (heuristic) |
Dismissed: The actual sink accesses wmm->client[i], not rule directly. wmm is derived from rule->wmm_ptr but valid_wmm(wmm) is called at line 844 before any wmm field accesses. Assuming valid_wmm validates the wmm buffer, this is a false positive.
Finding #8 — Category E — cross-function via set_wmm_rule() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 877 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Call site | line 885 — passes rule to set_wmm_rule() |
| Call snippet | set_wmm_rule(db, country, rule, rrule); |
| Pointer deref | rule-> line 854 |
| Sink snippet | wmm_rule->client[i].cw_max = ecw2cw(wmm->client[i].ecw & 0x0f); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #7 — wmm->client[i] access protected by valid_wmm() call at line 844.
Finding #9 — Category E — cross-function via set_wmm_rule() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 877 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Call site | line 885 — passes rule to set_wmm_rule() |
| Call snippet | set_wmm_rule(db, country, rule, rrule); |
| Pointer deref | rule-> line 855 |
| Sink snippet | wmm_rule->client[i].aifsn = wmm->client[i].aifsn; |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #7 — protected by valid_wmm().
Finding #10 — Category E — cross-function via set_wmm_rule() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 877 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Call site | line 885 — passes rule to set_wmm_rule() |
| Call snippet | set_wmm_rule(db, country, rule, rrule); |
| Pointer deref | rule-> line 857 |
| Sink snippet | 1000 * be16_to_cpu(wmm->client[i].cot); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #7 — protected by valid_wmm().
Finding #11 — Category E — cross-function via set_wmm_rule() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 877 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Call site | line 885 — passes rule to set_wmm_rule() |
| Call snippet | set_wmm_rule(db, country, rule, rrule); |
| Pointer deref | rule-> line 858 |
| Sink snippet | wmm_rule->ap[i].cw_min = ecw2cw((wmm->ap[i].ecw & 0xf0) >> 4); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #7 — wmm->ap[i] access protected by valid_wmm().
Finding #12 — Category E — cross-function via set_wmm_rule() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 877 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Call site | line 885 — passes rule to set_wmm_rule() |
| Call snippet | set_wmm_rule(db, country, rule, rrule); |
| Pointer deref | rule-> line 859 |
| Sink snippet | wmm_rule->ap[i].cw_max = ecw2cw(wmm->ap[i].ecw & 0x0f); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #7 — protected by valid_wmm().
Finding #13 — Category E — cross-function via set_wmm_rule() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 877 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Call site | line 885 — passes rule to set_wmm_rule() |
| Call snippet | set_wmm_rule(db, country, rule, rrule); |
| Pointer deref | rule-> line 860 |
| Sink snippet | wmm_rule->ap[i].aifsn = wmm->ap[i].aifsn; |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #7 — protected by valid_wmm().
Finding #14 — Category E — cross-function via set_wmm_rule() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 877 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Call site | line 885 — passes rule to set_wmm_rule() |
| Call snippet | set_wmm_rule(db, country, rule, rrule); |
| Pointer deref | rule-> line 861 |
| Sink snippet | wmm_rule->ap[i].cot = 1000 * be16_to_cpu(wmm->ap[i].cot); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as finding #7 — protected by valid_wmm().
regdb_query_country() — net/wireless/reg.c MIXED confidence=medium
The function derives 'coll' and 'rule' pointers from firmware-supplied 16-bit offset fields (shifted left by 2) with no validation that the resulting offsets + sizeof(*coll)/sizeof(*rule) fall within the firmware blob's bounds. regdom is locally kzalloc'd and findings tainting it via the line-919 source are false positives. The genuine bugs are: (1) coll pointer unvalidated against firmware size; (2) rule pointer unvalidated against firmware size; (3) coll->n_rules unvalidated before kzalloc_flex, potentially causing a massive allocation. | The function parses firmware-supplied data without validating that computed offsets (coll_ptr, rule_ptr) fall within the firmware buffer bounds. Findings about 'rrule' are false positives because rrule points into locally kzalloc'd memory (regdom->reg_rules[i]), not into the firmware buffer. Findings about 'rule' and 'coll' are genuine bugs: unvalidated firmware offsets could point outside the firmware buffer. The regulatory database is loaded from firmware files, making this an attack surface if a malicious firmware file is used. | Findings #1-4 trace taint from rule->wmm_ptr into wmm pointer, but valid_wmm(wmm) is called before the flagged sinks (lines 858-861), validating wmm before dereferencing. Findings #5-12 incorrectly treat rrule (pointer into locally-allocated regdom) as tainted; the allocation was sized to n_rules and i is bounded by n_reg_rules==n_rules, so no OOB. The real concern (unvalidated rule_ptr/coll_ptr offsets against db buffer bounds) is present but not directly what's flagged in these sinks.
Finding #1 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | coll |
| Pointer deref | coll->n_rules line 924 |
| Sink snippet | regdom = kzalloc_flex(*regdom, reg_rules, coll->n_rules); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds or kernel panic reading coll->n_rules from beyond firmware blob
Fix: Validate that ptr + sizeof(struct fwdb_collection) <= regdb_size before casting to struct fwdb_collection*
CVE pattern: firmware blob offset OOB — similar to CVE-2019-17133 style offset validation missing
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | regdom |
| Pointer deref | regdom->n_reg_rules line 928 |
| Sink snippet | regdom->n_reg_rules = coll->n_rules; |
| Possibly guarded | no |
Dismissed: regdom is allocated locally by kzalloc_flex; it is not derived from server/firmware offset arithmetic. The taint propagation from be16_to_cpu line 919 to regdom is spurious — false positive.
Finding #3 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | coll |
| Pointer deref | coll->n_rules line 928 |
| Sink snippet | regdom->n_reg_rules = coll->n_rules; |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds reading coll->n_rules from beyond firmware blob bounds
Fix: Validate ptr + sizeof(struct fwdb_collection) <= regdb_size before use
CVE pattern: firmware blob offset OOB
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | regdom |
| Pointer deref | regdom->alpha2 line 929 |
| Sink snippet | regdom->alpha2[0] = country->alpha2[0]; |
| Possibly guarded | no |
Dismissed: regdom->alpha2[0] is a write to a locally kzalloc'd struct. regdom is not derived from the be16_to_cpu offset. False positive.
Finding #5 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | regdom |
| Pointer deref | regdom->alpha2 line 930 |
| Sink snippet | regdom->alpha2[1] = country->alpha2[1]; |
| Possibly guarded | no |
Dismissed: Same as #4 — regdom->alpha2[1] write to locally-allocated struct. False positive.
Finding #6 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | regdom |
| Pointer deref | regdom->dfs_region line 931 |
| Sink snippet | regdom->dfs_region = coll->dfs_region; |
| Possibly guarded | no |
Dismissed: regdom->dfs_region is a field in the locally kzalloc'd regdom struct. False positive on the regdom side. The coll->dfs_region read is covered by finding #7.
Finding #7 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | coll |
| Pointer deref | coll->dfs_region line 931 |
| Sink snippet | regdom->dfs_region = coll->dfs_region; |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds reading coll->dfs_region beyond firmware blob
Fix: Validate ptr + sizeof(struct fwdb_collection) <= regdb_size before dereferencing coll
CVE pattern: firmware blob offset OOB
Finding #8 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | regdom |
| Loop | for_loop line 933 |
| Sink snippet | for (i = 0; i < regdom->n_reg_rules; i++) { |
| Possibly guarded | no |
Dismissed: The loop bound is regdom->n_reg_rules which was set from coll->n_rules. kzalloc_flex allocated exactly n_rules slots in regdom, so regdom->reg_rules[i] for i < n_reg_rules is within the allocated buffer. The real concern is the unvalidated coll pointer (finding #1/#3) and large n_rules causing big allocation, but the loop itself over regdom->reg_rules does not OOB the regdom allocation. False positive for this specific loop-bounds finding.
Finding #9 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | regdom |
| Pointer deref | regdom->n_reg_rules line 933 |
| Sink snippet | for (i = 0; i < regdom->n_reg_rules; i++) { |
| Possibly guarded | no |
Dismissed: regdom is locally allocated; reading regdom->n_reg_rules is reading from a locally-controlled struct field. False positive.
Finding #10 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | coll |
| Pointer deref | coll->len line 934 |
| Sink snippet | __be16 *rules_ptr = (void *)((u8 *)coll + ALIGN(coll->len, 2)); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds reading coll->len beyond firmware blob
Fix: Validate ptr + sizeof(struct fwdb_collection) <= regdb_size before dereferencing coll->len
CVE pattern: firmware blob offset OOB
Finding #11 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | regdom |
| Pointer deref | regdom->reg_rules line 937 |
| Sink snippet | struct ieee80211_reg_rule *rrule = ®dom->reg_rules[i]; |
| Possibly guarded | no |
Dismissed: regdom->reg_rules[i] — regdom is locally kzalloc'd with n_rules entries; accessing index i < n_reg_rules is within bounds. False positive.
Finding #12 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | rrule |
| Pointer deref | rrule->freq_range line 939 |
| Sink snippet | rrule->freq_range.start_freq_khz = be32_to_cpu(rule->start); |
| Possibly guarded | no |
Dismissed: rrule points into locally kzalloc'd regdom->reg_rules array; not derived from firmware offset arithmetic. False positive.
Finding #13 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 935 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Pointer deref | rule->start line 939 |
| Sink snippet | rrule->freq_range.start_freq_khz = be32_to_cpu(rule->start); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds or kernel panic reading rule->start from beyond firmware blob
Fix: Validate rule_ptr + sizeof(struct fwdb_rule) <= regdb_size before dereferencing rule
CVE pattern: firmware blob offset OOB
Finding #14 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | rrule |
| Pointer deref | rrule->freq_range line 940 |
| Sink snippet | rrule->freq_range.end_freq_khz = be32_to_cpu(rule->end); |
| Possibly guarded | no |
Dismissed: rrule is from locally-allocated regdom; false positive for the rrule taint chain from line 919.
Finding #15 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 935 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Pointer deref | rule->end line 940 |
| Sink snippet | rrule->freq_range.end_freq_khz = be32_to_cpu(rule->end); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds reading rule->end from beyond firmware blob
Fix: Validate rule_ptr + sizeof(struct fwdb_rule) <= regdb_size before dereferencing rule
CVE pattern: firmware blob offset OOB
Finding #16 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | rrule |
| Pointer deref | rrule->freq_range line 941 |
| Sink snippet | rrule->freq_range.max_bandwidth_khz = be32_to_cpu(rule->max_bw); |
| Possibly guarded | no |
Dismissed: rrule is from locally-allocated regdom. False positive.
Finding #17 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 935 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Pointer deref | rule->max_bw line 941 |
| Sink snippet | rrule->freq_range.max_bandwidth_khz = be32_to_cpu(rule->max_bw); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds reading rule->max_bw from beyond firmware blob
Fix: Validate rule_ptr + sizeof(struct fwdb_rule) <= regdb_size before dereferencing rule
CVE pattern: firmware blob offset OOB
Finding #18 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | rrule |
| Pointer deref | rrule->power_rule line 943 |
| Sink snippet | rrule->power_rule.max_antenna_gain = 0; |
| Possibly guarded | no |
Dismissed: rrule->power_rule.max_antenna_gain = 0 is a write to locally-allocated regdom. False positive.
Finding #19 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | rrule |
| Pointer deref | rrule->power_rule line 944 |
| Sink snippet | rrule->power_rule.max_eirp = be16_to_cpu(rule->max_eirp); |
| Possibly guarded | no |
Dismissed: rrule is from locally-allocated regdom. False positive.
Finding #20 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 935 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Pointer deref | rule->max_eirp line 944 |
| Sink snippet | rrule->power_rule.max_eirp = be16_to_cpu(rule->max_eirp); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds reading rule->max_eirp from beyond firmware blob
Fix: Validate rule_ptr + sizeof(struct fwdb_rule) <= regdb_size before dereferencing rule
CVE pattern: firmware blob offset OOB
Finding #21 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | rrule |
| Pointer deref | rrule->flags line 946 |
| Sink snippet | rrule->flags = 0; |
| Possibly guarded | no |
Dismissed: rrule = ®dom->reg_rules[i] where regdom is locally kzalloc'd. Writing to rrule->flags is a write to locally allocated memory, not a dereference of a firmware-offset-derived pointer. False positive: the taint tracer incorrectly propagated through n_rules to the loop index and then to regdom->reg_rules[i], but that memory is locally allocated.
Finding #22 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 935 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Pointer deref | rule->flags line 947 |
| Sink snippet | if (rule->flags & FWDB_FLAG_NO_OFDM) |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds or wild pointer dereference when firmware contains a crafted rule_ptr value pointing outside the firmware buffer
Fix: Before forming 'rule = (void *)((u8 *)db + rule_ptr)', validate that rule_ptr + sizeof(struct fwdb_rule) <= firmware_size. The firmware size should be tracked (e.g., passed as a parameter or stored globally alongside regdb).
CVE pattern: Firmware parsing OOB read via unvalidated offset
Finding #23 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | rrule |
| Pointer deref | rrule->flags line 948 |
| Sink snippet | rrule->flags |= NL80211_RRF_NO_OFDM; |
| Possibly guarded | no |
Dismissed: Same as finding #1: rrule points to locally allocated regdom->reg_rules[i]. False positive.
Finding #24 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 935 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Pointer deref | rule->flags line 949 |
| Sink snippet | if (rule->flags & FWDB_FLAG_NO_OUTDOOR) |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds or kernel crash reading rule->flags from out-of-bounds firmware offset
Fix: Validate rule_ptr + sizeof(struct fwdb_rule) <= firmware_size before accessing any rule fields.
CVE pattern: Firmware parsing OOB read via unvalidated offset
Finding #25 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | rrule |
| Pointer deref | rrule->flags line 950 |
| Sink snippet | rrule->flags |= NL80211_RRF_NO_OUTDOOR; |
| Possibly guarded | no |
Dismissed: rrule is locally allocated memory. False positive.
Finding #26 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 935 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Pointer deref | rule->flags line 951 |
| Sink snippet | if (rule->flags & FWDB_FLAG_DFS) |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds reading rule->flags from out-of-bounds firmware offset
Fix: Validate rule_ptr + sizeof(struct fwdb_rule) <= firmware_size before dereferencing rule.
CVE pattern: Firmware parsing OOB read via unvalidated offset
Finding #27 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | rrule |
| Pointer deref | rrule->flags line 952 |
| Sink snippet | rrule->flags |= NL80211_RRF_DFS; |
| Possibly guarded | no |
Dismissed: rrule is locally allocated memory. False positive.
Finding #28 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 935 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Pointer deref | rule->flags line 953 |
| Sink snippet | if (rule->flags & FWDB_FLAG_NO_IR) |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds reading rule->flags from out-of-bounds firmware offset
Fix: Validate rule_ptr + sizeof(struct fwdb_rule) <= firmware_size before dereferencing rule.
CVE pattern: Firmware parsing OOB read via unvalidated offset
Finding #29 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | rrule |
| Pointer deref | rrule->flags line 954 |
| Sink snippet | rrule->flags |= NL80211_RRF_NO_IR; |
| Possibly guarded | no |
Dismissed: rrule is locally allocated memory. False positive.
Finding #30 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 935 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Pointer deref | rule->flags line 955 |
| Sink snippet | if (rule->flags & FWDB_FLAG_AUTO_BW) |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds reading rule->flags from out-of-bounds firmware offset
Fix: Validate rule_ptr + sizeof(struct fwdb_rule) <= firmware_size before dereferencing rule.
CVE pattern: Firmware parsing OOB read via unvalidated offset
Finding #31 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | rrule |
| Pointer deref | rrule->flags line 956 |
| Sink snippet | rrule->flags |= NL80211_RRF_AUTO_BW; |
| Possibly guarded | no |
Dismissed: rrule is locally allocated memory. False positive.
Finding #32 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | rrule |
| Pointer deref | rrule->dfs_cac_ms line 958 |
| Sink snippet | rrule->dfs_cac_ms = 0; |
| Possibly guarded | no |
Dismissed: rrule->dfs_cac_ms is a write to locally allocated regdom->reg_rules[i]. False positive.
Finding #33 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 935 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Pointer deref | rule->len line 961 |
| Sink snippet | if (rule->len >= offsetofend(struct fwdb_rule, cac_timeout)) |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds reading rule->len from out-of-bounds firmware offset
Fix: Validate rule_ptr + sizeof(struct fwdb_rule) <= firmware_size before accessing rule->len. The check 'rule->len >= offsetofend(...)' assumes rule is already safely accessible.
CVE pattern: Firmware parsing OOB read via unvalidated offset
Finding #34 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | rrule |
| Pointer deref | rrule->dfs_cac_ms line 962 |
| Sink snippet | rrule->dfs_cac_ms = |
| Possibly guarded | no |
Dismissed: rrule->dfs_cac_ms is a write to locally allocated memory. False positive.
Finding #35 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 935 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Pointer deref | rule->cac_timeout line 963 |
| Sink snippet | 1000 * be16_to_cpu(rule->cac_timeout); |
| Possibly guarded | yes (heuristic) |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds reading rule->cac_timeout if rule points outside firmware buffer
Fix: First validate rule_ptr + rule->len <= firmware_size and rule_ptr + sizeof(struct fwdb_rule) <= firmware_size. The existing check on rule->len is insufficient because rule itself may be OOB.
CVE pattern: Firmware parsing OOB read via unvalidated offset
Finding #36 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 935 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Pointer deref | rule->len line 964 |
| Sink snippet | if (rule->len >= offsetofend(struct fwdb_rule, wmm_ptr)) |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds reading rule->len from out-of-bounds firmware offset
Fix: Validate rule_ptr + sizeof(struct fwdb_rule) <= firmware_size before accessing any rule fields including rule->len.
CVE pattern: Firmware parsing OOB read via unvalidated offset
Finding #37 — Category E — cross-function via set_wmm_rule() — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 935 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Call site | line 965 — passes rule to set_wmm_rule() |
| Call snippet | set_wmm_rule(db, country, rule, rrule); |
| Pointer deref | rule-> line 853 |
| Sink snippet | ecw2cw((wmm->client[i].ecw & 0xf0) >> 4); |
| Possibly guarded | yes (heuristic) |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in set_wmm_rule reading wmm fields from out-of-bounds firmware offset
Fix: Validate wmm_ptr (= be16_to_cpu(rule->wmm_ptr) << 2) + sizeof(struct fwdb_wmm_rule) <= firmware_size in set_wmm_rule before dereferencing wmm. The valid_wmm() call should include bounds checking against the firmware buffer size.
CVE pattern: Firmware parsing OOB read via unvalidated offset
Finding #38 — Category E — cross-function via set_wmm_rule() — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 935 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Call site | line 965 — passes rule to set_wmm_rule() |
| Call snippet | set_wmm_rule(db, country, rule, rrule); |
| Pointer deref | rule-> line 854 |
| Sink snippet | wmm_rule->client[i].cw_max = ecw2cw(wmm->client[i].ecw & 0x0f); |
| Possibly guarded | yes (heuristic) |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in set_wmm_rule reading wmm->client[i].ecw
Fix: Validate wmm_ptr + sizeof(struct fwdb_wmm_rule) <= firmware_size before accessing wmm fields.
CVE pattern: Firmware parsing OOB read via unvalidated offset
Finding #39 — Category E — cross-function via set_wmm_rule() — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 935 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Call site | line 965 — passes rule to set_wmm_rule() |
| Call snippet | set_wmm_rule(db, country, rule, rrule); |
| Pointer deref | rule-> line 855 |
| Sink snippet | wmm_rule->client[i].aifsn = wmm->client[i].aifsn; |
| Possibly guarded | yes (heuristic) |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in set_wmm_rule reading wmm->client[i].aifsn
Fix: Validate wmm_ptr + sizeof(struct fwdb_wmm_rule) <= firmware_size before accessing wmm fields.
CVE pattern: Firmware parsing OOB read via unvalidated offset
Finding #40 — Category E — cross-function via set_wmm_rule() — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 935 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Call site | line 965 — passes rule to set_wmm_rule() |
| Call snippet | set_wmm_rule(db, country, rule, rrule); |
| Pointer deref | rule-> line 857 |
| Sink snippet | 1000 * be16_to_cpu(wmm->client[i].cot); |
| Possibly guarded | yes (heuristic) |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in set_wmm_rule reading wmm->client[i].cot
Fix: Validate wmm_ptr + sizeof(struct fwdb_wmm_rule) <= firmware_size before accessing wmm fields.
CVE pattern: Firmware parsing OOB read via unvalidated offset
Finding #41 — Category E — cross-function via set_wmm_rule() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 935 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Call site | line 965 — passes rule to set_wmm_rule() |
| Call snippet | set_wmm_rule(db, country, rule, rrule); |
| Pointer deref | rule-> line 858 |
| Sink snippet | wmm_rule->ap[i].cw_min = ecw2cw((wmm->ap[i].ecw & 0xf0) >> 4); |
| Possibly guarded | yes (heuristic) |
Dismissed: Sink is wmm->ap[i] in set_wmm_rule(). wmm is derived from rule->wmm_ptr, but valid_wmm(wmm) is called at line 844 before any wmm dereference. The flagged sinks at 858-861 are after this validation gate.
Finding #42 — Category E — cross-function via set_wmm_rule() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 935 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Call site | line 965 — passes rule to set_wmm_rule() |
| Call snippet | set_wmm_rule(db, country, rule, rrule); |
| Pointer deref | rule-> line 859 |
| Sink snippet | wmm_rule->ap[i].cw_max = ecw2cw(wmm->ap[i].ecw & 0x0f); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as #1 — wmm validated by valid_wmm() before line 859 access.
Finding #43 — Category E — cross-function via set_wmm_rule() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 935 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Call site | line 965 — passes rule to set_wmm_rule() |
| Call snippet | set_wmm_rule(db, country, rule, rrule); |
| Pointer deref | rule-> line 860 |
| Sink snippet | wmm_rule->ap[i].aifsn = wmm->ap[i].aifsn; |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as #1 — wmm validated by valid_wmm() before line 860 access.
Finding #44 — Category E — cross-function via set_wmm_rule() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 935 |
| Taint snippet | unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; |
| Tainted var | rule |
| Call site | line 965 — passes rule to set_wmm_rule() |
| Call snippet | set_wmm_rule(db, country, rule, rrule); |
| Pointer deref | rule-> line 861 |
| Sink snippet | wmm_rule->ap[i].cot = 1000 * be16_to_cpu(wmm->ap[i].cot); |
| Possibly guarded | yes (heuristic) |
Dismissed: Same as #1 — wmm validated by valid_wmm() before line 861 access.
Finding #45 — Category E — cross-function via set_wmm_rule() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | rrule |
| Call site | line 965 — passes rrule to set_wmm_rule() |
| Call snippet | set_wmm_rule(db, country, rule, rrule); |
| Pointer deref | rrule-> line 853 |
| Sink snippet | ecw2cw((wmm->client[i].ecw & 0xf0) >> 4); |
| Possibly guarded | no |
Dismissed: rrule points into locally-allocated regdom (kzalloc_flex sized to n_rules). i is bounded by n_reg_rules==n_rules, so access is within allocation. Taint propagation through index into local allocation is a false positive.
Finding #46 — Category E — cross-function via set_wmm_rule() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | rrule |
| Call site | line 965 — passes rrule to set_wmm_rule() |
| Call snippet | set_wmm_rule(db, country, rule, rrule); |
| Pointer deref | rrule-> line 854 |
| Sink snippet | wmm_rule->client[i].cw_max = ecw2cw(wmm->client[i].ecw & 0x0f); |
| Possibly guarded | no |
Dismissed: Same as #5 — rrule is a pointer into locally-allocated regdom, not a server-controlled pointer.
Finding #47 — Category E — cross-function via set_wmm_rule() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | rrule |
| Call site | line 965 — passes rrule to set_wmm_rule() |
| Call snippet | set_wmm_rule(db, country, rule, rrule); |
| Pointer deref | rrule-> line 855 |
| Sink snippet | wmm_rule->client[i].aifsn = wmm->client[i].aifsn; |
| Possibly guarded | no |
Dismissed: Same as #5.
Finding #48 — Category E — cross-function via set_wmm_rule() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | rrule |
| Call site | line 965 — passes rrule to set_wmm_rule() |
| Call snippet | set_wmm_rule(db, country, rule, rrule); |
| Pointer deref | rrule-> line 857 |
| Sink snippet | 1000 * be16_to_cpu(wmm->client[i].cot); |
| Possibly guarded | no |
Dismissed: Same as #5.
Finding #49 — Category E — cross-function via set_wmm_rule() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | rrule |
| Call site | line 965 — passes rrule to set_wmm_rule() |
| Call snippet | set_wmm_rule(db, country, rule, rrule); |
| Pointer deref | rrule-> line 858 |
| Sink snippet | wmm_rule->ap[i].cw_min = ecw2cw((wmm->ap[i].ecw & 0xf0) >> 4); |
| Possibly guarded | no |
Dismissed: Same as #5.
Finding #50 — Category E — cross-function via set_wmm_rule() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | rrule |
| Call site | line 965 — passes rrule to set_wmm_rule() |
| Call snippet | set_wmm_rule(db, country, rule, rrule); |
| Pointer deref | rrule-> line 859 |
| Sink snippet | wmm_rule->ap[i].cw_max = ecw2cw(wmm->ap[i].ecw & 0x0f); |
| Possibly guarded | no |
Dismissed: Same as #5.
Finding #51 — Category E — cross-function via set_wmm_rule() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | rrule |
| Call site | line 965 — passes rrule to set_wmm_rule() |
| Call snippet | set_wmm_rule(db, country, rule, rrule); |
| Pointer deref | rrule-> line 860 |
| Sink snippet | wmm_rule->ap[i].aifsn = wmm->ap[i].aifsn; |
| Possibly guarded | no |
Dismissed: Same as #5.
Finding #52 — Category E — cross-function via set_wmm_rule() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 919 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | rrule |
| Call site | line 965 — passes rrule to set_wmm_rule() |
| Call snippet | set_wmm_rule(db, country, rule, rrule); |
| Pointer deref | rrule-> line 861 |
| Sink snippet | wmm_rule->ap[i].cot = 1000 * be16_to_cpu(wmm->ap[i].cot); |
| Possibly guarded | no |
Dismissed: Same as #5.
set_wmm_rule() — net/wireless/reg.c BUG confidence=medium
The wmm_ptr offset derived from the firmware regulatory database is never validated against the total database buffer size before being used to form a pointer. valid_wmm() validates the semantic values inside the struct (cw_min < cw_max, aifsn >= 1) but does NOT verify that wmm_ptr + sizeof(struct fwdb_wmm_rule) <= db_end. If a malformed/malicious regulatory database provides an out-of-range wmm_ptr, both valid_wmm() and the subsequent loop would read out-of-bounds memory. valid_wmm() does access the same fields (covering client[] and ap[] arrays via the ac[] cast), so it would also fault first — but it doesn't constitute a bounds check.
Finding #1 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 841 |
| Taint snippet | wmm_ptr = be16_to_cpu(rule->wmm_ptr) << 2; |
| Tainted var | wmm |
| Pointer deref | wmm->client line 853 |
| Sink snippet | ecw2cw((wmm->client[i].ecw & 0xf0) >> 4); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds or general protection fault in valid_wmm/set_wmm_rule when processing a malformed regulatory database with out-of-range wmm_ptr
Fix: Before forming the wmm pointer, validate: wmm_ptr + sizeof(struct fwdb_wmm_rule) <= db_size (where db_size is the total size of the loaded regulatory database blob). Add this check before the valid_wmm() call.
CVE pattern: Firmware blob offset OOB read via unchecked offset arithmetic
Finding #2 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 841 |
| Taint snippet | wmm_ptr = be16_to_cpu(rule->wmm_ptr) << 2; |
| Tainted var | wmm |
| Pointer deref | wmm->client line 854 |
| Sink snippet | wmm_rule->client[i].cw_max = ecw2cw(wmm->client[i].ecw & 0x0f); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in set_wmm_rule when processing malformed regulatory database
Fix: Validate wmm_ptr + sizeof(struct fwdb_wmm_rule) <= db_size before dereferencing wmm.
CVE pattern: Firmware blob offset OOB read via unchecked offset arithmetic
Finding #3 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 841 |
| Taint snippet | wmm_ptr = be16_to_cpu(rule->wmm_ptr) << 2; |
| Tainted var | wmm |
| Pointer deref | wmm->client line 855 |
| Sink snippet | wmm_rule->client[i].aifsn = wmm->client[i].aifsn; |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in set_wmm_rule when processing malformed regulatory database
Fix: Validate wmm_ptr + sizeof(struct fwdb_wmm_rule) <= db_size before dereferencing wmm.
CVE pattern: Firmware blob offset OOB read via unchecked offset arithmetic
Finding #4 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 841 |
| Taint snippet | wmm_ptr = be16_to_cpu(rule->wmm_ptr) << 2; |
| Tainted var | wmm |
| Pointer deref | wmm->client line 857 |
| Sink snippet | 1000 * be16_to_cpu(wmm->client[i].cot); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in set_wmm_rule when processing malformed regulatory database
Fix: Validate wmm_ptr + sizeof(struct fwdb_wmm_rule) <= db_size before dereferencing wmm.
CVE pattern: Firmware blob offset OOB read via unchecked offset arithmetic
Finding #5 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 841 |
| Taint snippet | wmm_ptr = be16_to_cpu(rule->wmm_ptr) << 2; |
| Tainted var | wmm |
| Pointer deref | wmm->ap line 858 |
| Sink snippet | wmm_rule->ap[i].cw_min = ecw2cw((wmm->ap[i].ecw & 0xf0) >> 4); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in set_wmm_rule when processing malformed regulatory database
Fix: Validate wmm_ptr + sizeof(struct fwdb_wmm_rule) <= db_size before dereferencing wmm.
CVE pattern: Firmware blob offset OOB read via unchecked offset arithmetic
Finding #6 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 841 |
| Taint snippet | wmm_ptr = be16_to_cpu(rule->wmm_ptr) << 2; |
| Tainted var | wmm |
| Pointer deref | wmm->ap line 859 |
| Sink snippet | wmm_rule->ap[i].cw_max = ecw2cw(wmm->ap[i].ecw & 0x0f); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in set_wmm_rule when processing malformed regulatory database
Fix: Validate wmm_ptr + sizeof(struct fwdb_wmm_rule) <= db_size before dereferencing wmm.
CVE pattern: Firmware blob offset OOB read via unchecked offset arithmetic
Finding #7 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 841 |
| Taint snippet | wmm_ptr = be16_to_cpu(rule->wmm_ptr) << 2; |
| Tainted var | wmm |
| Pointer deref | wmm->ap line 860 |
| Sink snippet | wmm_rule->ap[i].aifsn = wmm->ap[i].aifsn; |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in set_wmm_rule when processing malformed regulatory database
Fix: Validate wmm_ptr + sizeof(struct fwdb_wmm_rule) <= db_size before dereferencing wmm.
CVE pattern: Firmware blob offset OOB read via unchecked offset arithmetic
Finding #8 — Category E — BUG oob_read
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 841 |
| Taint snippet | wmm_ptr = be16_to_cpu(rule->wmm_ptr) << 2; |
| Tainted var | wmm |
| Pointer deref | wmm->ap line 861 |
| Sink snippet | wmm_rule->ap[i].cot = 1000 * be16_to_cpu(wmm->ap[i].cot); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | yes |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in set_wmm_rule when processing malformed regulatory database
Fix: Validate wmm_ptr + sizeof(struct fwdb_wmm_rule) <= db_size before dereferencing wmm.
CVE pattern: Firmware blob offset OOB read via unchecked offset arithmetic
valid_country() — net/wireless/reg.c FP confidence=high
valid_country() performs careful sequential validation: line 706 checks that coll+offsetofend(n_rules) fits in the buffer before reading coll->len or coll->n_rules; line 710-711 then checks the full extent including the rules array; line 715 validates coll->len is large enough. All struct field accesses on coll occur after these guards. The loop at line 720 is bounded by the check at line 711 (coll->n_rules*2 bytes of rules_ptr array verified in-bounds). valid_rule() is itself the validator for rule_ptr — its internal accesses are validation logic, not vulnerable sinks.
Finding #1 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 700 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | coll |
| Pointer deref | coll->len line 710 |
| Sink snippet | if ((u8 *)coll + ALIGN(coll->len, 2) + |
| Possibly guarded | yes (heuristic) |
Dismissed: Line 706 checks (u8*)coll + offsetofend(typeof(*coll), n_rules) > data + size before any field access. This covers coll->len at line 710. Cannot construct counterexample: any ptr that would place coll->len outside the buffer would fail line 706.
Finding #2 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 700 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | coll |
| Pointer deref | coll->n_rules line 711 |
| Sink snippet | (coll->n_rules * 2) > data + size) |
| Possibly guarded | yes (heuristic) |
Dismissed: Line 706 checks offsetofend(typeof(*coll), n_rules) which includes n_rules in bounds verification. Cannot construct counterexample that passes line 706 but allows OOB read of n_rules.
Finding #3 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 700 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | coll |
| Pointer deref | coll->len line 715 |
| Sink snippet | if (coll->len < offsetofend(struct fwdb_collection, dfs_region)) |
| Possibly guarded | yes (heuristic) |
Dismissed: By line 715, both prior checks at 706 and 710-711 have passed, ensuring coll->len is safely readable. This access is protected.
Finding #4 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 700 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | coll |
| Pointer deref | coll->len line 718 |
| Sink snippet | rules_ptr = (void *)((u8 *)coll + ALIGN(coll->len, 2)); |
| Possibly guarded | yes (heuristic) |
Dismissed: Line 718 access to coll->len is after all three guards (706, 710-711, 715) have passed. Fully protected.
Finding #5 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | be16_to_cpu() line 700 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | coll |
| Loop | for_loop line 720 |
| Sink snippet | for (i = 0; i < coll->n_rules; i++) { |
| Possibly guarded | yes (heuristic) |
Dismissed: Line 710-711 verifies coll->n_rules*2 bytes fit in the buffer before the loop. The loop iterates exactly coll->n_rules times over rules_ptr[i], which was validated to fit. Each iteration calls valid_rule() which validates the rule_ptr value independently.
Finding #6 — Category E — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 700 |
| Taint snippet | unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; |
| Tainted var | coll |
| Pointer deref | coll->n_rules line 720 |
| Sink snippet | for (i = 0; i < coll->n_rules; i++) { |
| Possibly guarded | yes (heuristic) |
Dismissed: coll->n_rules at line 720 is safe to read; line 706 ensures offsetofend(n_rules) bytes are in bounds. Same argument as finding #2.
Finding #7 — Category E — cross-function via valid_rule() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 721 |
| Taint snippet | u16 rule_ptr = be16_to_cpu(rules_ptr[i]); |
| Tainted var | rule_ptr |
| Call site | line 723 — passes rule_ptr to valid_rule() |
| Call snippet | if (!valid_rule(data, size, rule_ptr)) |
| Pointer deref | rule_ptr-> line 676 |
| Sink snippet | if ((u8 *)rule + sizeof(rule->len) > data + size) |
| Possibly guarded | no |
Dismissed: valid_rule() IS the validation function for rule_ptr. The access at line 676 inside valid_rule() is the validation check itself, not a vulnerable sink. False positive by definition — the function exists to validate its argument.
Finding #8 — Category E — cross-function via valid_rule() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 721 |
| Taint snippet | u16 rule_ptr = be16_to_cpu(rules_ptr[i]); |
| Tainted var | rule_ptr |
| Call site | line 723 — passes rule_ptr to valid_rule() |
| Call snippet | if (!valid_rule(data, size, rule_ptr)) |
| Pointer deref | rule_ptr-> line 680 |
| Sink snippet | if (rule->len < offsetofend(struct fwdb_rule, max_bw)) |
| Possibly guarded | yes (heuristic) |
Dismissed: Access at line 680 in valid_rule() is guarded by the check at line 676. valid_rule() validates its argument before this access. False positive — validation function internals.
Finding #9 — Category E — cross-function via valid_rule() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 721 |
| Taint snippet | u16 rule_ptr = be16_to_cpu(rules_ptr[i]); |
| Tainted var | rule_ptr |
| Call site | line 723 — passes rule_ptr to valid_rule() |
| Call snippet | if (!valid_rule(data, size, rule_ptr)) |
| Pointer deref | rule_ptr-> line 682 |
| Sink snippet | if (rule->len >= offsetofend(struct fwdb_rule, wmm_ptr)) { |
| Possibly guarded | yes (heuristic) |
Dismissed: Access at line 682 is guarded by lines 676 and 680. This is internal validation logic in valid_rule().
Finding #10 — Category E — cross-function via valid_rule() — false positive
| Category | Cat E — tainted pointer dereference (->field access beyond packet bounds) |
|---|---|
| Taint source | be16_to_cpu() line 721 |
| Taint snippet | u16 rule_ptr = be16_to_cpu(rules_ptr[i]); |
| Tainted var | rule_ptr |
| Call site | line 723 — passes rule_ptr to valid_rule() |
| Call snippet | if (!valid_rule(data, size, rule_ptr)) |
| Pointer deref | rule_ptr-> line 683 |
| Sink snippet | u32 wmm_ptr = be16_to_cpu(rule->wmm_ptr) << 2; |
| Possibly guarded | yes (heuristic) |
Dismissed: Access at line 683 (rule->wmm_ptr) is inside the branch guarded by rule->len >= offsetofend(struct fwdb_rule, wmm_ptr) at line 682, which itself is after the sizeof(rule->len) check at line 676. Fully protected validation logic.
cfg80211_parse_ml_elem_sta_data() — net/wireless/scan.c FP confidence=high
The function has solid validation discipline: ieee80211_mle_type_ok() validates the ML element, cfg80211_defrag_mle() validates and bounds-checks all sta_prof entries, ieee80211_mle_basic_sta_prof_size_ok() checks each profile before use, and explicit size guards protect buffer writes. All three findings are false positives: #1 because link_id uses a 4-bit mask fitting u8; #2 because cfg80211_rnr_info_for_mld_ap returns u8 directly; #3 because reporter_rnr->datalen is u8 (max 255) and protected by an explicit overflow guard.
Finding #1 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 3044 |
| Taint snippet | link_id = u16_get_bits(control, |
| Tainted var | link_id |
| Truncation | line 3044: 16 → 8-bit u8 |
| Sink snippet | link_id = u16_get_bits(control, |
| Possibly guarded | no |
Dismissed: IEEE80211_MLE_STA_CONTROL_LINK_ID is a 4-bit mask (0x000F). u16_get_bits() applies this mask before assignment to u8 link_id, so the result is always 0-15, well within u8 range. No counterexample can produce a value exceeding u8 max given the mask. The truncation is safe by construction.
Finding #2 — Category H — false positive
| Category | Cat H — server value → narrow integer type (silent truncation) |
|---|---|
| Taint source | le16_to_cpu() line 3075 |
| Taint snippet | use_for = cfg80211_rnr_info_for_mld_ap(tx_data->ie, |
| Tainted var | use_for |
| Truncation | line 3075: 16 → 8-bit u8 |
| Sink snippet | use_for = cfg80211_rnr_info_for_mld_ap(tx_data->ie, |
| Possibly guarded | no |
Dismissed: cfg80211_rnr_info_for_mld_ap() is declared as returning u8 (line 2830), so assigning its return to u8 use_for involves no truncation at all. The scanner's claim that this is a 16-bit-to-8-bit truncation is incorrect. The taint chain through link_id is also broken since link_id is already bounded to 4 bits. False positive.
Finding #3 — Category B — INTEGER OVERFLOW — false positive
| Category | Cat B — integer overflow: sizeof(struct element) + reporter_rnr->datalen |
|---|---|
| Taint source | le16_to_cpu() line 2978 |
| Taint snippet | control = le16_to_cpu(ml_elem->control); |
| Tainted var | reporter_rnr |
| Overflow expr | sizeof(struct element) + reporter_rnr->datalen |
| Safe fix | kmalloc_array() or check_mul_overflow() |
| Sink | memcpy() line 3179 (arg 2, role=size_mul_overflow) |
| Sink snippet | memcpy(new_ie + data.ielen, reporter_rnr, |
| Possibly guarded | yes (heuristic) |
Dismissed: reporter_rnr is kernel-generated by cfg80211_gen_reporter_rnr(), not a direct server-supplied buffer. reporter_rnr->datalen is of type u8 (struct element field), bounded to 0-255. sizeof(struct element)+datalen is at most 257, no integer overflow possible. The explicit guard at lines 3175-3177 checks that data.ielen + sizeof(struct element) + reporter_rnr->datalen <= IEEE80211_MAX_DATA_LEN before the memcpy. No counterexample exists that bypasses this guard while causing OOB.
cfg80211_get_p2p_attr() — net/wireless/util.c FP confidence=high
The function is careful about both source and destination bounds. For source buffer safety, copy is computed as min(attr_len, iedatalen), where iedatalen is the remaining validated IE data length — so the read never goes past the input buffer. For destination buffer safety, the memcpy size argument is min(bufsize, copy), where bufsize tracks remaining output space and is decremented after each copy — so the write never exceeds the output buffer. The taint from get_unaligned_le16() is fully contained by these two independent guards.
Finding #1 — Category B — false positive
| Category | Cat B — server value → size/alloc argument |
|---|---|
| Taint source | get_unaligned_le16() line 2004 |
| Taint snippet | attr_len = get_unaligned_le16(iedata + 1); |
| Tainted var | copy |
| Sink | memcpy() line 2013 (arg 2, role=size) |
| Sink snippet | memcpy(out, iedata, min(bufsize, copy)); |
| Possibly guarded | no |
Dismissed: Two independent bounds checks protect the memcpy: (a) copy = min_t(unsigned int, attr_len, iedatalen) at line 2008 caps the read against remaining IE data, preventing OOB read of the source buffer; (b) min(bufsize, copy) at line 2013 caps the write against remaining destination buffer space, preventing OOB write. A concrete counterexample — e.g., attr_len=0xFFFF, iedatalen=50, bufsize=10 — results in copy=50 and memcpy size=10, both safe. No counterexample that bypasses both guards simultaneously can be constructed.
ioctl_private_iw_point() — net/wireless/wext-priv.c FP confidence=high
extra_size is computed by get_priv_descr_and_size() from kernel-internal device/command descriptors, not from user-supplied data. The kzalloc() allocation uses the same extra_size, so copy_from_user uses it against a buffer of exactly that size. For copy_to_user, adjust_priv_size() may modify extra_size but is constrained by kernel descriptor masks and should not exceed the original allocation. Both findings are false positives.
Finding #1 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_from_user() line 160 |
| Taint snippet | if (copy_from_user(extra, iwp->pointer, extra_size)) { |
| Tainted var | extra_size |
| Unvalidated size | copy_from_user() arg 2 line 160 — size extra_size |
| Sink snippet | if (copy_from_user(extra, iwp->pointer, extra_size)) { |
| Possibly guarded | no |
Dismissed: extra_size comes from get_priv_descr_and_size() — a kernel-internal computation based on device command descriptors, not user input. The buffer extra is allocated with kzalloc(extra_size), so copy_from_user(..., extra_size) stays within bounds. No counterexample possible since extra_size is not user-controlled.
Finding #2 — Category G2 — false positive
| Category | Cat G2 — unvalidated size argument to copy_from/to_user |
|---|---|
| Taint source | copy_to_user() line 177 |
| Taint snippet | if (copy_to_user(iwp->pointer, extra, extra_size)) |
| Tainted var | extra_size |
| Unvalidated size | copy_to_user() arg 2 line 177 — size extra_size |
| Sink snippet | if (copy_to_user(iwp->pointer, extra, extra_size)) |
| Possibly guarded | no |
Dismissed: After adjust_priv_size(), extra_size is based on descr->get_args (kernel constant mask) and iwp->length (set by kernel handler post-execution). The adjusted size is structurally bounded by the descriptor's IW_PRIV_SIZE_MASK and should not exceed the originally allocated buffer size. No user-controlled path can inflate extra_size beyond the kzalloc'd buffer.
xfrm_input() — net/xfrm/xfrm_input.c FP confidence=high
The function has sound validation discipline. The taint chain reported by the scanner is incorrect: 'mark' (derived from be32_to_cpu at line 565) is not the same as 'x->props.family' used at line 756. 'x' is an xfrm_state retrieved from the kernel's own internal state database via xfrm_input_state_lookup(), not from the incoming network packet. Furthermore, xfrm_state_afinfo_get_rcu() itself validates its 'family' argument against NPROTO before using it as an array subscript, and the caller checks for NULL return.
Finding #1 — Category C — cross-function via xfrm_state_afinfo_get_rcu() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | be32_to_cpu() line 565 |
| Taint snippet | mark = be32_to_cpu(XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip6->parms.i_key); |
| Tainted var | x |
| Call site | line 756 — passes x to xfrm_state_afinfo_get_rcu() |
| Call snippet | afinfo = xfrm_state_afinfo_get_rcu(x->props.family); |
| Subscript (in callee) | [] line 3127 |
| Sink snippet | return rcu_dereference(xfrm_state_afinfo[family]); |
| Possibly guarded | yes (heuristic) |
Dismissed: The scanner incorrectly propagates taint from 'mark' (set via be32_to_cpu at line 565) to 'x' (returned by xfrm_input_state_lookup). The value x->props.family used at line 756 originates from a kernel-internal xfrm_state struct, not from the network packet. Additionally, xfrm_state_afinfo_get_rcu() validates family >= NPROTO before indexing, and the caller handles the NULL return with 'if (likely(afinfo))'. No counterexample can be constructed: the taint chain is broken at the state lookup boundary.
xfrmi4_err() — net/xfrm/xfrm_interface_core.c FP confidence=high
The static analyzer over-approximates taint by treating the return value of xfrm_state_lookup() as tainted because its argument (spi) was derived from network data. However, xfrm_state_lookup() uses spi only as a lookup key into the kernel's internal xfrm state table; the returned xfrm_state struct and its fields (including if_id used in xfrmi_hash()) are kernel-controlled, not populated from the incoming packet. The NULL check at line 627-628 ensures x is valid before use.
Finding #1 — Category C — cross-function via xfrmi_lookup() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 608 |
| Taint snippet | spi = htonl(ntohs(ipch->cpi)); |
| Tainted var | x |
| Call site | line 630 — passes x to xfrmi_lookup() |
| Call snippet | xi = xfrmi_lookup(net, x); |
| Subscript (in callee) | [] line 159 |
| Sink snippet | for_each_xfrmi_rcu(xfrmn->xfrmi[xfrmi_hash(x->if_id)], xi) { |
| Possibly guarded | no |
Dismissed: The taint flows from network-supplied spi into xfrm_state_lookup() as a lookup key, but the returned xfrm_state *x is a kernel-internal object whose fields are set by kernel configuration (xfrm policy/state management), not by the incoming packet. The field x->if_id used as xfrmi_hash() argument is kernel-controlled. xfrm_state_lookup() returns NULL (checked at line 627) if no matching state exists, so any non-NULL x is a valid kernel struct. The scanner is performing taint propagation across a lookup boundary incorrectly — this is a well-known false positive pattern where lookup-key taint does not propagate to lookup result fields. No counterexample is possible since x->if_id cannot be influenced by the attacker.
xfrmi6_err() — net/xfrm/xfrm_interface_core.c FP confidence=high
The taint chain is broken at xfrm_state_lookup(). The network-supplied 'spi' value is used as a hash table lookup key, not as a direct memory offset. The returned 'x' pointer is a kernel-managed struct xfrm_state object whose 'if_id' field is set by local kernel configuration (not derived from packet data). The xfrmi_hash(x->if_id) array subscript uses kernel-controlled data, not server-supplied data. Additionally, the NULL check on x at line 681-682 properly guards against lookup failure.
Finding #1 — Category C — cross-function via xfrmi_lookup() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohs() line 669 |
| Taint snippet | spi = htonl(ntohs(ipch->cpi)); |
| Tainted var | x |
| Call site | line 684 — passes x to xfrmi_lookup() |
| Call snippet | xi = xfrmi_lookup(net, x); |
| Subscript (in callee) | [] line 159 |
| Sink snippet | for_each_xfrmi_rcu(xfrmn->xfrmi[xfrmi_hash(x->if_id)], xi) { |
| Possibly guarded | no |
Dismissed: The scanner incorrectly propagates taint through xfrm_state_lookup(). While 'spi' is network-supplied and used as a lookup key, the returned 'struct xfrm_state *x' is a kernel-internal object stored in the xfrm state hash table. Its 'if_id' field is set at state creation time by local configuration, not derived from packet content. xfrmi_hash() produces a bounded index into xfrmn->xfrmi[]. No counterexample exists because x->if_id is not attacker-controlled. The NULL check at line 681 guards against failed lookups. This is a false positive due to taint over-approximation across the hash table lookup boundary.
__input_process_payload() — net/xfrm/xfrm_iptfs.c FP confidence=high
The function properly validates IPv4/IPv6 header fields before use (checking remaining bytes, validating iplen >= iphlen, etc.). The taint chain reported here is a scanner artifact: it tracks skb as tainted because it was allocated with iplen derived from be16_to_cpu(payload_len), then flags sp->xvec[sp->len++] inside xfrm_input() as an array subscript vulnerability. However, the array index sp->len is a kernel-internal security path depth counter, explicitly bounds-checked against XFRM_MAX_DEPTH before the array access — it is entirely independent of the network-supplied IPv6 payload_len field.
Finding #1 — Category C — cross-function via xfrm_input() — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | be16_to_cpu() line 1019 |
| Taint snippet | iplen = be16_to_cpu(((struct ipv6hdr *)hbytes)->payload_len); |
| Tainted var | skb |
| Call site | line 1191 — passes skb to xfrm_input() |
| Call snippet | if (xfrm_input(skb, 0, 0, -2)) |
| Subscript (in callee) | [] line 612 |
| Sink snippet | sp->xvec[sp->len++] = x; |
| Possibly guarded | yes (heuristic) |
Dismissed: The scanner propagates taint from be16_to_cpu(payload_len) → iplen → skb (pointer allocation) → xfrm_input(skb) → sp->xvec[sp->len++]. The array subscript sp->len is the security path depth, not derived from iplen. It is protected by the explicit check 'if (sp->len == XFRM_MAX_DEPTH) goto drop' at line 587 of xfrm_input(). No counterexample exists: sp->len cannot exceed XFRM_MAX_DEPTH regardless of what payload_len contains. This is a taint-tracking false positive caused by treating the skb pointer as uniformly tainted and then flagging unrelated array accesses inside xfrm_input().
iptfs_input_ordered() — net/xfrm/xfrm_iptfs.c FP confidence=high
The function carefully validates the IPTFS header from network data, uses skb_copy_seq_read() which enforces skb bounds internally, and the 'data' value returned from iptfs_reassem_cont() is validated per its postcondition. The loop in __input_process_payload() uses 'while (data < tail)' where tail=skb->len, making it a naturally bounded traversal — data controls when the loop runs, not an array index without bounds.
Finding #1 — Category F — cross-function via __input_process_payload() — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohs() line 1273 |
| Taint snippet | blkoff = ntohs(ipth->block_offset); |
| Tainted var | data |
| Call site | line 1288 — passes data to __input_process_payload() |
| Call snippet | consumed = __input_process_payload(x, data, &skbseq, &sublist); |
| Loop | while_loop line 974 |
| Sink snippet | while (data < tail) { |
| Possibly guarded | no |
Dismissed: The tainted 'data' value is used as the starting offset in a while loop 'while (data < tail)' where tail=skb->len. This is a standard bounded traversal: if data >= skb->len the loop simply does not execute. Within the loop, remaining=tail-data is always positive, and skb_copy_seq_read() validates offsets against skb bounds internally. The iptfs_reassem_cont() validator postcondition establishes that the returned data offset is valid within the skb. No counterexample exists that would allow data to cause OOB — the loop condition itself is the bound. False positive.
xfrm_replay_advance_bmp() — net/xfrm/xfrm_replay.c MIXED confidence=medium
Finding #1 (loop) is a false positive because diff < replay_window is checked before the loop and per-iteration modulo keeps bitnr in bounds. Finding #2 is a potential real bug: in the else branch (seq <= replay_esn->seq), diff has no upper bound check; if pos < diff and (diff - pos) > replay_window, the expression replay_window - (diff - pos) underflows as u32, yielding a huge bitnr and nr values, causing OOB write to bmp[].
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 262 |
| Taint snippet | u32 seq = ntohl(net_seq); |
| Tainted var | diff |
| Loop | for_loop line 274 |
| Sink snippet | for (i = 1; i < diff; i++) { |
| Possibly guarded | yes (heuristic) |
Dismissed: The guard at line 273 (diff < replay_esn->replay_window) ensures the loop runs at most replay_window-1 times. Per-iteration modulo at line 275 keeps bitnr in [0, replay_window-1], so nr = bitnr>>5 stays within bmp[] bounds. No counterexample possible: any diff >= replay_window takes the else branch entirely, skipping the loop.
Finding #2 — Category C — BUG oob_write
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohl() line 262 |
| Taint snippet | u32 seq = ntohl(net_seq); |
| Tainted var | nr |
| Subscript | [] line 299 |
| Sink snippet | replay_esn->bmp[nr] |= (1U << bitnr); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in xfrm_replay_advance_bmp; kernel crash or heap corruption writing to replay_esn->bmp[nr] where nr is derived from underflowed u32 arithmetic
Fix: In the else branch (line 291-294), add a bounds check on diff: if diff > replay_window, the sequence number is outside the window and should be silently dropped or handled — e.g., 'if (diff >= replay_esn->replay_window) return;' before computing bitnr. Also verify the expression replay_window - (diff - pos) cannot underflow by checking diff - pos <= replay_window.
CVE pattern: Integer underflow leading to out-of-bounds array write in replay window bitmap
xfrm_replay_advance_esn() — net/xfrm/xfrm_replay.c MIXED confidence=high
Finding #1 is a false positive because the loop body uses modulo arithmetic that bounds array indices within the valid bmp range. Finding #2 is a real bug in path 3 (line 598): when pos < diff and diff > replay_window, an unsigned integer underflow occurs producing an out-of-bounds bmp index.
Finding #1 — Category F — false positive
| Category | Cat F — server value → loop iteration count |
|---|---|
| Taint source | ntohl() line 563 |
| Taint snippet | seq = ntohl(net_seq); |
| Tainted var | diff |
| Loop | for_loop line 575 |
| Sink snippet | for (i = 1; i < diff; i++) { |
| Possibly guarded | yes (heuristic) |
Dismissed: The loop only executes when diff < replay_esn->replay_window (line 574). Inside the loop, bitnr is computed as (pos+i) % replay_esn->replay_window, which constrains bitnr to [0, replay_window-1] and thus nr = bitnr>>5 to [0, (replay_window-1)>>5]. No counterexample possible — modulo bounds are tight.
Finding #2 — Category C — BUG oob_write
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohl() line 563 |
| Taint snippet | seq = ntohl(net_seq); |
| Tainted var | nr |
| Subscript | [] line 605 |
| Sink snippet | replay_esn->bmp[nr] |= (1U << bitnr); |
| Possibly guarded | no |
| Server-supplied | yes |
| Check present | no |
| Check sufficient | no |
Symptom: BUG: KASAN: slab-out-of-bounds in xfrm_replay_advance_esn; kernel crash or heap corruption via OOB write to replay_esn->bmp[]
Fix: In the else branch of line 595-598, add a check that diff <= replay_esn->replay_window before computing bitnr. If diff > replay_window and pos < diff-replay_window, clamp or handle appropriately. Specifically: if (diff >= replay_esn->replay_window) bitnr = (replay_esn->replay_window - 1); else bitnr = replay_esn->replay_window - (diff - pos); — or restructure to ensure bitnr always uses modulo arithmetic: bitnr = (replay_esn->replay_window + pos - diff % replay_esn->replay_window) % replay_esn->replay_window.
CVE pattern: Integer underflow leading to out-of-bounds array write in replay window bitmap management
xfrm_replay_check_bmp() — net/xfrm/xfrm_replay.c FP confidence=high
The function correctly bounds `bitnr` to [0, replay_window-1] via the guard at line 231 (`diff >= replay_window` → error). Both branches of the pos/diff comparison yield a value strictly less than replay_window, so `nr = bitnr >> 5` is strictly less than ceil(replay_window/32), which is the allocated size of bmp[]. No counterexample can be constructed that passes the guard yet causes OOB.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohl() line 219 |
| Taint snippet | u32 seq = ntohl(net_seq); |
| Tainted var | nr |
| Subscript | [] line 245 |
| Sink snippet | if (replay_esn->bmp[nr] & (1U << bitnr)) |
| Possibly guarded | no |
Dismissed: The guard `if (diff >= replay_esn->replay_window) goto err` at line 231 ensures diff < replay_window. Combined with pos = (seq-1) % replay_window (so pos < replay_window), both computation branches yield bitnr in [0, replay_window-1]. Therefore nr = bitnr >> 5 is in [0, (replay_window-1)/32], which is within the allocated bmp[] array sized at ceil(replay_window/32) u32 words. No counterexample exists; the scanner flagged tainted arithmetic that is actually properly bounded.
xfrm_replay_check_esn() — net/xfrm/xfrm_replay.c FP confidence=high
The array subscript 'nr' is derived from 'bitnr >> 5', where 'bitnr' is strictly less than replay_window after the guard at line 484 (diff >= replay_esn->replay_window returns error). Since the bmp[] array is allocated with replay_window/32 words, nr is always a valid index. The taint flows through ntohl() but is fully bounded before the array access.
Finding #1 — Category C — false positive
| Category | Cat C — server value → array subscript |
|---|---|
| Taint source | ntohl() line 458 |
| Taint snippet | u32 seq = ntohl(net_seq); |
| Tainted var | nr |
| Subscript | [] line 498 |
| Sink snippet | if (replay_esn->bmp[nr] & (1U << bitnr)) |
| Possibly guarded | no |
Dismissed: The guard at line 484 (if diff >= replay_esn->replay_window → goto err) ensures diff < wsize after that point. pos = (seq-1) % wsize so pos < wsize. In both branches: bitnr = (pos-diff)%wsize < wsize (if pos>=diff), or bitnr = wsize-(diff-pos) where 1<=diff-pos<=wsize-1 so 1<=bitnr<=wsize-1 < wsize (else branch). Therefore nr = bitnr>>5 < wsize/32, which is exactly the allocated size of bmp[]. No counterexample can be constructed — the check is tight and sufficient.