CVE-2019-11478: Sack Slowness
In June 2019, Netflix engineer Jonathan Looney disclosed three related bugs in how Linux handles TCP Selective Acknowledgments (SACK). The most famous one is CVE-2019-11477 (“SACK Panic”), which crashes the kernel. This post is about its quieter sibling, CVE-2019-11478.
CVE-2019-11478 lets a remote attacker make a Linux server break its queue of unacknowledged TCP data into thousands of tiny pieces, just by sending specially shaped SACK packets on a normal connection. On kernels older than 4.15 this makes every later SACK slow to process and burns CPU. It is a pure denial of service (DoS) bug, and the fix is a five line memory check in one function, tcp_fragment().
How TCP keeps track of data
TCP gives every byte a number and makes the sender hold each byte until the receiver confirms it. That holding area is what this bug attacks. Every byte a TCP sender sends gets a 32-bit number called its sequence number. If a connection starts at sequence 1000 and sends 500 bytes, those bytes are numbered 1000 to 1499. The receiver uses these numbers to put data back in order. The receiver answers with an ACK number which is the next byte it expects. ACK=1500 means “I have every byte before 1500.” This is called cumulative because one number confirms everything before it.
Suppose bytes 1500 to 1999 are lost, but bytes 2000–2999 arrive. The receiver cannot say “I have 2000–2999” with a plain ACK. It can only repeat ACK = 1500. A repeated ACK number is called a duplicate ACK. It tells the sender something is missing, but not what arrived after the gap. What arrived after the gap is what SACK covers which we will see in a while.
The Linux sender keeps two pointers for every connection:
snd_una(“send unacknowledged”) which is the oldest byte the receiver has not confirmed yet.snd_nxt(“send next”) is the next new byte the sender will send.
Everything between them, [snd_una, snd_nxt), is in flight, sent, but not yet confirmed. The sender cannot throw away in flight bytes, because it may need to send them again if they were lost. Linux keeps them in the retransmission queue (“rtx queue” for short). Bytes leave this queue only when a cumulative ACK moves snd_una past them. Keep one thing in mind that if an attacker can keep snd_una from moving, everything the server sent stays in the retransmission queue and the attacker gets to play with it.
Selective Acknowledgment (SACK)
Now lets discuss SACK which lets the receiver say exactly which byte ranges it got after a gap. For example the receiver received everything till 1000 and then 1500 till 1999 but nothing between 1000 and 1500. The key point for this bug is that the receiver chooses those SACK ranges, and the sender mostly trusts them. You can check whether SACK is enabled on your
Linux machine using the following command:

SACK is defined in RFC 2018. It fixes the duplicate ACK problem above. Instead of only repeating ACK = 1500, the receiver can add “and I also have 2000–2999.” The sender then resends only the missing bytes which in this case will be from 1500 till 1999. Both sides send a SACK Permitted option (TCP option kind 4, length 2) in their SYN and SYN-ACK packets during the handshake. Linux enables this by default (net.ipv4.tcp_sack = 1).
Below is a screenshot of a packet capture from wireshark:
Here we see the the ACK number is 158829 and SACK is from 160229 till 164429. This means the next byte the receiver is expecting in sequence should be 158829 but it has received bytes starting from 160229 up until 164428 too.
After the handshake, the receiver can attach a SACK option (kind 5, length 2 + 8×n bytes) to any ACK. It carries n blocks. Each block is two 32-bit sequence numbers:
- the left edge: the first byte received in that range
- the right edge: the byte just after the last byte received
Example: ACK = 1500, SACK = [2000, 3000) means “I have everything before 1500, and also bytes 2000–2999.” TCP options can use at most 40 bytes. Four blocks take 2 + 32 = 34 bytes, so 4 is the maximum. If the connection also uses TCP timestamps (10 bytes plus 2 bytes of padding), only 3 blocks fit. Linux defines TCP_NUM_SACKS as 4. This can be checked using the following command:
grep -rn "TCP_NUM_SACKS" /usr/include/ /usr/src/ 2>/dev/null
We can see it’s defined in multiple places:

Keep in mind that SACK is only an advice. RFC 2018 allows the receiver to later throw away data it SACKed. So the sender must keep SACKed bytes in the retransmission queue until a cumulative ACK covers them. It only marks them as SACKed.
Background: the Linux pieces involved
We will be analyzing kernel version 4.14.126 for this CVE, and four details make this bug possible:
- Every queued chunk of data costs fixed memory. In the sender when the kernel holds data that has not been acknowledged yet, it keeps each chunk in a small metadata structure (the skb, described below). That structure takes roughly 1 KB of memory, no matter how much data it holds. A chunk with 1 byte of data still costs about 1 KB.
- Chunks normally hold many packets. To save that overhead, the kernel groups data into large chunks, up to 64 KB each. One chunk can hold about 45 packets’ worth of data. So in normal use, the number of chunks stays small.
- Splitting a chunk skips the normal memory limits. Sometimes the kernel must cut one chunk into two, for example when a SACK block (a report from the receiver saying which bytes arrived) starts in the middle of it. When it does this, it creates the new chunk without checking the socket’s send buffer limit. The send buffer limit is what would normally stop the list from growing without bound. Without that check, nothing does.
sk_sndbufis the maximum memory the kernel allows for one connection’s queued data. It is set bySO_SNDBUFor tuned automatically withinnet.ipv4.tcp_wmem. Typical default maximum is 4 MB. Each chunk costs its metadata (about 1 KB) plus its data, and all of that counts against this limit. Each split therefore adds memory that nothing stops. - The queue is a linked list searched one item at a time. To find the chunk that holds a given byte, the kernel starts at the front of the list and checks each item in turn. With few items this is fast. With tens of thousands of items, every lookup is slow.
The first three details cause the memory waste as the chunks are many, small, and unchecked. The fourth detail is what turns the bug into a CPU problem as each new ACK forces a long search through that growing list.
Lets take a look at some kernel specific terms before jumping into further details on the CVE:
sk_buff (skb) the kernel’s packet container
The skb is defined in struct sk_buff in include/linux/skbuff.h. It is a single structure that describes one packet’s worth of data.
The metadata struct (struct sk_buff itself)…
This is the small header that describes the skb. It holds next and prev pointers that link the current skb into a list, sk which is the socket that owns the skb, truesize which is the total memory this skb uses and many more.
The linear area (the “head”)…
This is one contiguous block of memory, pointed to by head. It holds headers and sometimes some data. The region between data and tail is the part in use.
struct sk_buff {
.....
sk_buff_data_t tail;
sk_buff_data_t end;
unsigned char *head,
*data;
unsigned int truesize;
.....
}
tail marks where the useful data ends. The pointers in the block are arranged like this:

head: the start of the whole memory block.data: the start of the real data. Everything fromheadtodatais free space.tail: the end of the real data.datatotailis the data the skb holds (its length istail - data).end: the end of the whole memory block.tailtoendis free space.
Both ends have free space because the kernel adds protocol headers in front of data as it goes down the stack. Leaving room at the front (headroom) lets it move data backward and write a header without copying the payload. Room at the back (tailroom) lets it append data without reallocating. So tail is needed because the kernel must know where the data stops, and it cannot assume that the block ends at end.
Page fragments (skb_shared_info->frags)…
skb_shared_info sits at the end of the head block, and its frags[] array points to pages of data. Most of an large skb’s data lives here, so the head stays small.
What truesize means
truesize is the amount of memory the kernel actually reserved for an skb. It is not the number of data bytes in it. An skb’s memory is spread across three places which we just discussed:
- The metadata struct (
struct sk_buff): a fixed-size header allocated for every skb. - The head block: the data area, plus room for
skb_shared_infoat the end. It is allocated even for a tiny amount of data. - Page fragments: pages holding larger payloads, used only when the data does not fit in the head.
truesize adds these up. The kernel needs this number because the goal is to limit real memory use, not data bytes. Counting only data bytes would let a connection hold huge amounts of memory by sending tiny pieces. The kernel adds each skb’s truesize to the socket’s sk_wmem_queued total. That total is what is compared with sk_sndbuf. So the send-buffer limit is a limit on truesize, not on data bytes. Part of truesize does not depend on how much data the skb holds. The metadata struct and the head block are paid for in full, even if the skb holds one byte. That means:
- A 64 KB skb with about 45 segments pays this fixed cost once, for 64 KB of data.
- Forty-five 1-byte skbs pay the fixed cost 45 times, for 45 bytes of data.
So many small skbs use far more memory per byte than a few big ones. The bug depends on this as splits create small skbs, each with its full fixed cost, and each one counts against the buffer.
MSS, TSO/GSO and pcount
- MSS (Maximum Segment Size) is the largest payload one TCP packet can carry. Each side announces its MSS in the SYN. A typical Ethernet value is 1460 bytes, or 1448 when timestamps are on.
- TSO/GSO (TCP and Generic Segmentation Offload): instead of building one skb per packet, Linux builds big skbs of up to 64 KB (
GSO_MAX_SIZE). The network card (TSO) or a late software step (GSO) cuts each big skb into MSS-sized packets on the way out. - pcount:
tcp_skb_pcount(skb)is how many MSS-sized packets (“segments”) one skb holds. A 64 KB skb with an MSS of 1448 holds about 45 segments.
So a healthy retransmission queue holds a few big skbs, each with many segments.
Send-buffer accounting
1. sk_sndbuf, the limit
This is the most memory the connection may use for data it has not yet had acknowledged. It can be set by the app with SO_SNDBUF, or the kernel can adjust it automatically within the range in net.ipv4.tcp_wmem. The default range 4096 16384 4194304 means at least 4 KB, starting at 16 KB, and growing up to 4 MB.
2. sk_wmem_queued, the current usage
This is a running total of the truesize of every skb the connection is holding. When an skb is queued, its truesize is added. When it is freed, it is subtracted. In 4.14.126 there is one list, not two, so the total covers that single queue.
3. The check in send()
When an application writes data, tcp_sendmsg() checks whether sk_wmem_queued has reached sk_sndbuf. If it has, the application waits until ACKs free memory. This is the brake that keeps a connection from holding unlimited memory.
The gap in the split path
The split path is different. When SACK processing needs to cut an skb in two, it calls tcp_fragment(), and that function runs inside the kernel, not in the app’s call. The code shows what it does:
skb_unclone()makes a private copy of the skb if another part of the kernel is sharing it. If that fails, the function returns-ENOMEM. This is the only failure check on this path.sk_stream_alloc_skb(sk, nsize, gfp, true)allocates the new piece. The last argumenttrueisforce_schedule. With it on, the memory is charged to the socket even when TCP’s global limit or the socket’s own buffer is full.
The function never reads sk_wmem_queued or compares it with sk_sndbuf. So the split creates a new skb past the limit, and nothing refuses it.
/* net/ipv4/tcp_output.c, v4.14.126, lines 1277-1281 */
if (skb_unclone(skb, gfp))
return -ENOMEM;
/* Get a new skb... force flag on. */
buff = sk_stream_alloc_skb(sk, nsize, gfp, true);
That last true is force_schedule. Inside sk_stream_alloc_skb() (net/ipv4/tcp.c, line 859), it chooses between two memory calls:
/* net/ipv4/tcp.c, v4.14.126, around line 859 */
if (force_schedule) {
mem_scheduled = true;
sk_forced_mem_schedule(sk, skb->truesize);
} else {
mem_scheduled = sk_wmem_schedule(sk, skb->truesize);
}
sk_wmem_schedule() can say no when TCP’s global memory limit (net.ipv4.tcp_mem) is reached. sk_forced_mem_schedule() never says no. It exists so that critical packets, such as FIN, can still go out under memory pressure. Using it in tcp_fragment() makes sense for normal loss recovery, where a split must not fail. It also means that a split caused by an attacker can never be refused.
Retransmission Queue in 4.14.126
In 4.14.126, sent and unsent skbs share one linked list sk_write_queue means there is no separate retransmission list as there is present in newer kernel versions:
- Skbs are kept in order, from the oldest unacknowledged byte to the newest unsent byte.
- A pointer,
sk_send_head, marks the first skb that has not been sent yet. Everything before it has been sent and is waiting for an ACK means it involves the concept of a retransmission queue but is not one actually as they weren’t introduced in kernel versions prior to 4.15. - To find the skb that holds a given sequence number, the kernel walks the list from a starting point, one skb at a time, checking each skb’s
end_seq. That is O(n) and the time grows with the number of skbs.
The list is easy to understand and cheap when it is short. It becomes a problem when the number of skbs grows into the tens of thousands. Eric Dumazet’s later commit 75c119afe14f (“tcp: implement rb-tree based retransmit queue”, part of 4.15) explains that a list is fine when N is below about 500, but “things get messy when N is the order of 100,000”. At about 40 ns per cache miss, “a full scan can use 4 ms”.
How SACK is handled function by function
When an ACK arrives with SACK blocks, the work happens in one main function, tcp_sacktag_write_queue() (net/ipv4/tcp_input.c, line 1662 in v4.14.126). It is called with the list of SACK blocks the receiver sent. Each block is a byte range the receiver already has, for example “bytes 5000 to 8000 arrived, but 3000 to 5000 is still missing”. The function walks the retransmission queue and marks the matching skbs as SACKed.
Step 1: limit the number of blocks
/* net/ipv4/tcp_input.c, v4.14.126, line 1672 */
int num_sacks = min(TCP_NUM_SACKS, (ptr[1] - TCPOLEN_SACK_BASE) >> 3);
A SACK option has room for at most 4 blocks (TCP_NUM_SACKS is 4, defined in include/linux/tcp.h, line 119). Each block takes 8 bytes. The min() makes sure the loop never reads more than 4 blocks, even if the option header claims more. That is a safety check, and it is cheap.
Step 2: check each block is sane tcp_is_sackblock_valid()
For each block, the kernel checks that it fits between snd_una (the oldest byte still waiting for an ACK) and snd_nxt (the next byte to be sent).
if (!tcp_is_sackblock_valid(tp, dup_sack, sp[used_sacks].start_seq, sp[used_sacks].end_seq))
A block outside that window is ignored. This check is simple arithmetic with before() and after(), which handle 32-bit sequence number wraparound. This check does not limit how many skbs a valid block can touch. The attacker can send blocks that are all valid, so every one of them is processed.
Step 3: find the first skb for a block (tcp_sacktag_skip(), line 1621)
tcp_sacktag_skip() is called from tcp_sacktag_write_queue() in three places:
- line 1787 (derived): the block starts before a cached range, so the kernel skips to the block’s start
- line 1813 (derived): the rest of the block is already covered by the cache, so the kernel skips to the cache’s end
- line 1824 (derived): the general case, and the one that matters for this bug
skb = tcp_sacktag_skip(skb, sk, state, start_seq);In
tcp_sacktag_skip’s body we do:tcp_for_write_queue_from(skb, sk) { if (skb == tcp_send_head(sk)) break; if (after(TCP_SKB_CB(skb)->end_seq, skip_to_seq)) break; state->fack_count += tcp_skb_pcount(skb); } return skb;
This function finds the first skb that contains the start of a SACK block. It works like counting boxes on a shelf until you reach the one you want.
The walk
The queue is a row of skbs in byte order. The function starts at the first one and looks at each in turn:
- Does this skb end before the block starts? Then it is not part of the block. Move to the next one, and add its packet count to
fack_count. - Does this skb end after the block starts? Then it contains the start of the block. Stop, and return this skb.
- Did we reach
tcp_send_head? That is the first unsent skb. Nothing in the block can be past it, so stop.
Why it adds to fack_count
fack_count is a running total of packets the receiver has, counted from the front of the queue. Each skb it passes adds its packet count. Later, the kernel uses this total to decide which packets are probably lost, based on how many packets arrived ahead of them.
Example The queue holds 5 skbs, each 10 bytes, covering bytes 0 to 50. A block starts at byte 32.
- Skb covering 0 to 10: ends at 10, which is before 32. Skip it.
- Skb covering 10 to 20: ends at 20, before 32. Skip it.
- Skb covering 20 to 30: ends at 30, before 32. Skip it.
- Skb covering 30 to 40: ends at 40, after 32. Stop. This is the first skb of the block.
The function walked 3 skbs to get there.
Why it is O(n) The number of steps equals the number of skbs before the block. There is no index, so the walk starts from the front every time, and nothing from this walk is saved for the next block. If the queue has 50,000 skbs and the block starts near the end, the walk takes about 50,000 steps. A red black tree in 4.15 replaces this walk with a lookup that takes about log₂(n) steps.
Step 4: walk the skbs a block covers (tcp_sacktag_walk(), line 1543)
Once the first skb is found, tcp_sacktag_walk() moves forward over every skb that the block covers.
skb = tcp_sacktag_skip(skb, sk, state, start_seq);
walk:
skb = tcp_sacktag_walk(skb, sk, next_dup, state,
start_seq, end_seq, dup_sack);
It stops at the first skb that starts at or after the block’s end:
/* net/ipv4/tcp_input.c, v4.14.126, tcp_sacktag_walk(), simplified */
tcp_for_write_queue_from(skb, sk) {
if (skb == tcp_send_head(sk))
break;
if (!before(TCP_SKB_CB(skb)->seq, end_seq))
break;
/* mark the skb as SACKed, count it, update the lost/SACK state */
}
This walk goes through the skbs the block covers and tags each one as received. It works like the earlier walk, but instead of skipping skbs, it marks them.
The walk
The function starts at the first skb of the block, the one found by tcp_sacktag_skip(). For each skb, it checks one thing that does this skb start before the block ends?
- Yes: the skb is inside the block. Mark it, then move to the next skb.
- No: the skb starts at or after the block’s end, so it is outside the block. Stop.
- Reached
tcp_send_head: stop, because unsent skbs cannot be covered by a SACK.
What “mark” means
Each skb has a sacked field in its control block. Marking sets a flag in that field, meaning “the receiver has this data.” The flag is TCPCB_SACKED_ACKED for normal data. If the skb had been retransmitted, the kernel uses TCPCB_SACKED_RETRANS instead, so it knows the retransmitted copy arrived.
Example A block covers bytes 32 to 60. The queue has these skbs:
- 30 to 40: starts at 30, before 60. Mark it.
- 40 to 50: starts at 40, before 60. Mark it.
- 50 to 60: starts at 50, before 60. Mark it.
- 60 to 70: starts at 60, not before 60. Stop.
Skb 1 starts at 30, before the block’s start of 32. Only bytes 32 to 40 are in the block, so the kernel split that skb earlier, in the step that calls tcp_fragment(). After the split, the piece from 32 to 40 is marked. The piece from 30 to 32 stays unmarked.
One thing which got me confused while analyzing this was what does first skb of the block mean when each SACK block range lives inside an skb? The answer to this question is that a SACK block’s range does not have to fit inside one skb. The range can cover part of one skb, all of several skbs, or some of the first and last.
Step 5: split skbs that only partly match (tcp_match_skb_to_sack(), line 1166)
Sometimes a block starts or ends in the middle of an skb. The kernel then has to split that skb in two, so that the part inside the block can be marked and the part outside stays unmarked. That is done by tcp_match_skb_to_sack(), which calls tcp_fragment() for the split. tcp_match_skb_to_sack() is called only from inside tcp_sacktag_walk(), at two places in v4.14.126 (net/ipv4/tcp_input.c):
- Line 1565, in the first half of the walk
if (next_dup && before(TCP_SKB_CB(skb)->seq, next_dup->end_seq)) { in_sack = tcp_match_skb_to_sack(sk, skb, next_dup->start_seq, next_dup->end_seq); if (in_sack > 0) dup_sack = true; } - Line 1587, in the second half
if (in_sack <= 0) { tmp = tcp_shift_skb_data(sk, skb, state, start_seq, end_seq, dup_sack); if (tmp) { if (tmp != skb) { skb = tmp; continue; } in_sack = 0; } else { in_sack = tcp_match_skb_to_sack(sk, skb, start_seq, end_seq); } }
Both calls sit in the same loop. tcp_sacktag_walk() walks the skbs the block covers, and for each one it asks tcp_match_skb_to_sack() how the skb lines up with the block. If the block starts or ends partway through that skb, the match function splits it with tcp_fragment().
tcp_match_skb_to_sack has the call:
/* net/ipv4/tcp_input.c, v4.14.126, around line 1205 */
err = tcp_fragment(sk, skb, pkt_len, mss, GFP_ATOMIC);
The split creates a new skb, so the queue gets one skb longer. This is the step the attacker wants that every split adds one skb and its metadata memory.
Step 6: merge when possible (tcp_shift_skb_data(), line 1402)
The kernel tries to avoid growing the queue. tcp_shift_skb_data() takes SACKed bytes from the current skb and moves them into the previous skb, when both have the same SACK state. Moving bytes is cheap, and it lets the old skb be freed. If the two states do not match, the function falls back to the normal path (tcp_write_queue_prev()), and the skb stays where it is.
Why the walk repeats for every block
Steps 3 to 5 run once per SACK block. A single ACK can carry 4 blocks, and every ACK can carry new blocks. The kernel does not remember where the last block ended, except through the “highest SACK” hint. That hint only helps for blocks above the highest byte already SACKed. An attacker avoids this by sending blocks below that point, so every block forces a walk from the head of the queue.
The split function: tcp_fragment(), line by line
tcp_fragment() is in net/ipv4/tcp_output.c, starting at line 1261 in v4.14.126. Its job is to cut one skb into two at a byte position. It is used for normal retransmission work, and in this bug it is also used for SACK splits. Here is what it does, in order.
**1. Allocate the new piece **
buff = sk_stream_alloc_skb(sk, nsize, gfp, true);
The true is force_schedule. The memory is taken even when the socket is over its send buffer limit. No check against sk_sndbuf happens before this line.
2. Charge the memory to the socket
sk->sk_wmem_queued += buff->truesize;
sk_mem_charge(sk, buff->truesize);
The new piece’s memory is added to the socket’s total. This is bookkeeping only, so the total can go past sk_sndbuf without any effect.
3. Set the sequence numbers
TCP_SKB_CB(buff)->seq = TCP_SKB_CB(skb)->seq + len;
TCP_SKB_CB(buff)->end_seq = TCP_SKB_CB(skb)->end_seq;
TCP_SKB_CB(skb)->end_seq = TCP_SKB_CB(buff)->seq;
The old skb now ends at the cut point, and the new skb covers the rest.
4. Copy the SACK state
TCP_SKB_CB(buff)->sacked = TCP_SKB_CB(skb)->sacked;
The new piece keeps the same SACK mark as the original.
5. Move the data
skb_split(skb, buff, len);
The data after the cut point moves into the new skb. This is the copy or split work that costs CPU.
6. Insert into the list
tcp_insert_write_queue_after(skb, buff, sk);
The new skb goes into the linked list right after the old one. The list is now one node longer, so the next SACK walk has one more step.
The CVE link: steps 1 and 2 add memory without a limit check, and step 6 makes the list longer. Repeated splits grow both.
Why a list plus unlimited splits is a problem?
The bug is the combination of three facts on 4.14.126:
- Splits are not limited. Every SACK block can cause one or more
tcp_fragment()calls. Nothing counts how many skbs one connection has created in its queue. - Splits are not checked against memory.
tcp_fragment()allocates with force on, so the send buffer limit (sk_sndbuf) is never enforced on this path. - Finding a block costs time proportional to the queue length. The queue is one linked list, and every block walks it from the head (Step 3 in the SACK section).
Together these mean the queue can grow without any bound set by the kernel, and the time to process each ACK grows with it.
The loop that makes it expensive
One ACK with 4 SACK blocks does this:
- For each block, walk from the queue head to the block (O(n) in the number of skbs).
- Walk the covered skbs (O(k), where k is the number of skbs in the block).
- Maybe split one skb at each edge, which adds one skb (and one list node) for the next walk.
The number of skbs, n, is what the attacker controls. If the attacker makes the receiver’s SACK blocks land on tiny pieces, each split creates another skb. After many ACKs, n can reach tens of thousands. Each new ACK then walks tens of thousands of nodes, several times.
Why small pieces are the key
A normal queue holds about 45 segments per 64 KB skb, so n stays small. The attacker forces the kernel to keep small skbs. Splitting produces small pieces, and the MSS floor sets the smallest allowed piece size. In 4.14.126 that floor is 48 bytes (tcp_output.c, line 1445: if (mss_now < 48) mss_now = 48;).
How an attacker uses it
SO now we know that the kernel does O(n) work per SACK block, and n is not bounded, so a remote peer can make each ACK cost more and more CPU time while the connection holds more and more memory. It does so in the following ways:
- Open the connection and let the victim send a lot of data. The victim’s linked list/ retransmission queue fills with skbs that are waiting for ACKs.
- Hold back some bytes in the middle of the window. The attacker acknowledges the bytes before the gap, and withholds a few bytes in the middle. The receiver sends SACK blocks for the bytes after the gap.
- Send SACK blocks that cut the existing skbs into smaller pieces. Each block starts or ends inside an skb, so
tcp_match_skb_to_sack()callstcp_fragment()for each edge. One more skb is created, and it is not limited bysk_sndbuf. - Keep the new blocks below the highest SACKed byte. The highest-SACK hint then cannot shorten the walk. Every block starts from the head of the queue.
- Repeat. Each ACK adds more splits and more list nodes, so the walk gets longer. The attacker can keep the connection alive and keep sending ACKs, and the cost grows with each round.
Fix status on the linked list kernels
The fix for 4.14.126 is not a list change. The list stays. The fix limits the memory side, so the queue cannot grow past the send buffer through splits.
Before the split, tcp_fragment() now checks how much memory the socket already holds for queued data:
/* conceptual form of the 4.14.127 check, in tcp_fragment() before the allocation */
if (sk->sk_wmem_queued > sk->sk_sndbuf >> 1)
return -ENOMEM;
In plain words if the queue already uses more than half of the send buffer, refuse to split. The attacker can no longer add skbs past that point, so the queue stops growing. Ordinary loss recovery still works, because a normal retransmission queue holds far less than half the buffer.
Lab Setup
The lab for this CVE can be found at https://github.com/jinjucat/CVE-2019-11478.