Networking Subsystem Details
SKB Buffer Operations
skb_put(), skb_push(), and skb_pull() modify the data boundaries of a
socket buffer. Each operation has a bounds check that triggers a kernel panic
on violation:
skb_put(skb, len)extends the tail. Panics viaskb_over_panic()ifskb->tail > skb->end.skb_push(skb, len)prepends to head. Panics viaskb_under_panic()ifskb->data < skb->head.skb_pull(skb, len)consumes from head. CallsBUG()ifskb->len < skb->data_len.
Code that constructs or modifies packets must validate lengths before calling these functions. Passing untrusted or unchecked lengths causes a kernel panic (DoS) — the bounds check fires before memory is corrupted, so it is a crash rather than a silent overflow, but it is still a bug.
SKB Shared and Cloned Buffers
SKBs can be shared (skb->users > 1) or cloned (skb_cloned()). Modifying
a shared or cloned SKB corrupts other users of the same buffer.
skb_shared(skb)returns true whenrefcount_read(&skb->users) != 1skb_cloned(skb)returns true when the data area is shared with another SKB
Use skb_unshare(skb, gfp) to get an exclusive copy. If the buffer is
cloned, it copies the SKB and frees the original (even on allocation
failure). If not cloned, it returns the original unchanged. Always use the
returned pointer — the input pointer may have been freed. A NULL return
means allocation failed and the original SKB is already gone.
Header Linearization
Packet headers may span paged fragments and cannot be safely dereferenced
without first ensuring the bytes are in the linear region (skb->data).
pskb_may_pull(skb, len) guarantees at least len bytes are contiguous
in the linear part, pulling from fragments if necessary.
if (!pskb_may_pull(skb, sizeof(struct iphdr)))
return -EINVAL;
iph = ip_hdr(skb); /* safe: header is now in linear region */
Any code that parses protocol headers from received packets must call
pskb_may_pull() before dereferencing header pointers.
Socket Locking vs Socket Release
Two commonly confused functions:
release_sock(sk)releases the socket lock acquired bylock_sock(). It processes the backlog queue and wakes waiters. The socket remains alive.sock_release(sock)closes and destroys the socket structure, releasing the protocol stack and associated inode.
There is no function called socket_release() in the kernel.
After release_sock(), the socket is still valid but unlocked — other
threads may now operate on it. After sock_release(), the socket structure
is freed and must not be accessed.
Socket Reference Counting
Socket lifetime is managed through sk_refcnt:
sock_hold(sk)incrementssk->sk_refcntsock_put(sk)decrementssk->sk_refcntand callssk_free()when it reaches zero
A socket can outlive its file descriptor. Code that holds a pointer to a
socket outside the file descriptor's lifetime must hold a reference with
sock_hold() and release it with sock_put().
Netfilter Hook Ownership
NF_HOOK() and NF_HOOK_COND() pass an SKB through the netfilter hook
chain. The hook verdict determines what happens to the SKB:
| Verdict | Meaning | SKB Ownership |
|---|---|---|
NF_ACCEPT |
Continue processing | okfn() is called with the SKB |
NF_DROP |
Reject packet | Netfilter frees the SKB via kfree_skb_reason() |
NF_STOLEN |
Hook consumed packet | Hook took ownership |
In all cases, the caller of NF_HOOK() loses ownership of the SKB and must
not access it after the call.
Device pointers (in, out) passed to NF_HOOK() must remain valid
throughout hook traversal. Set skb->dev before calling NF_HOOK() when
the routing path depends on it.
Buffer Handoff Safety
Once an SKB is passed to another subsystem (queued, enqueued, handed to a protocol handler), the caller loses ownership. Accessing the SKB after handoff is a use-after-free because the receiver may free it at any time, including before the handoff function returns.
Byte Order Conversions
Network protocols use big-endian byte order. The kernel uses __be16,
__be32, and __be64 types to annotate network-order values. Common byte
order bugs:
- Comparing a
__be16port with a host-order constant withouthtons() - Performing arithmetic on network-order values without converting first
- Double-converting (applying
htons()to an already network-order value)
Sparse catches these at build time via __bitwise type annotations
(enabled when Sparse defines __CHECKER__; run with make C=1).
RCU Protection for Routing
Routing table lookups (FIB lookups, dst entries) are protected by RCU.
Callers must hold rcu_read_lock() during the lookup and while accessing
the result:
rcu_read_lock();
reason = ip_route_input_noref(skb, dst, src, dscp, devin);
if (!reason) {
skb_dst_force(skb); /* take refcount if needed beyond RCU */
if (!skb_dst(skb))
reason = SKB_DROP_REASON_NOT_SPECIFIED;
}
rcu_read_unlock();
skb_dst_set_noref() stores an RCU-protected dst entry without taking a
reference — it asserts that rcu_read_lock() or rcu_read_lock_bh() is
held. If the dst needs to survive beyond the RCU read-side critical section,
use skb_dst_force() to upgrade to a refcounted reference.
Per-CPU Network Statistics
Per-CPU network statistics can race with BH (bottom half) processing. The SNMP stat macros handle this at the appropriate level:
SNMP_INC_STATS()/SNMP_ADD_STATS()usethis_cpu_inc()/this_cpu_add()which are safe for single-word counters on all architecturesSNMP_ADD_STATS64()/SNMP_UPD_PO_STATS64()wrap updates inlocal_bh_disable()/local_bh_enable()and useu64_statsseqcounts on 32-bit systems (#if BITS_PER_LONG==32) where a 64-bit update is not atomic
Driver-specific statistics should follow the guidelines in
Documentation/networking/statistics.rst.
Packet Type Constants
The skb->pkt_type field classifies received packets:
| Constant | Value | Meaning |
|---|---|---|
PACKET_HOST |
0 | Destined for this host |
PACKET_BROADCAST |
1 | Link-layer broadcast |
PACKET_MULTICAST |
2 | Link-layer multicast |
PACKET_OTHERHOST |
3 | Destined for another host (promiscuous) |
PACKET_OUTGOING |
4 | Outgoing packet |
PACKET_LOOPBACK |
5 | MC/BRD frame looped back |
Special Port Constants
VMADDR_PORT_ANY is defined as -1U (0xFFFFFFFF) in
include/uapi/linux/vm_sockets.h for the vsock subsystem. Port allocation
logic that iterates or wraps around port ranges must explicitly exclude this
sentinel value to avoid binding to the wildcard port.
Quick Checks
- Validate packet lengths before
skb_put()/skb_push()/skb_pull() - Call
pskb_may_pull()before dereferencing protocol headers - Check
skb_shared()/skb_cloned()before modifying SKB data - Verify
htons()/ntohs()conversions on all port and protocol comparisons - Hold
rcu_read_lock()during routing table lookups - Use BH-safe stat update macros for per-CPU network counters
- Do not access an SKB after handing it to another subsystem
- Mask status/flag bits before exact-value comparisons on packet fields
- Check for flag value collisions when adding flags shared across subsystems