Tối ưu hóa hiệu suất truyền lại gói tin trên các mạng diện rộng (WAN) luôn là bài toán trọng tâm của các kỹ sư mạng. Trong nhiều thập kỷ đầu tiên của Internet, giao thức TCP truyền thống hoạt động phụ thuộc hoàn toàn vào cơ chế xác nhận lũy kế (Cumulative ACKs) theo đặc tả RFC 793. Khi chỉ một gói tin đơn lẻ trong cửa sổ truyền tải bị thất lạc, bên gửi buộc phải truyền lại gói tin bị mất cùng toàn bộ các gói tin theo sau, gây lãng phí băng thông nghiêm trọng.

Nhằm khắc phục nhược điểm này, tổ chức IETF đã chuẩn hóa TCP Selective Acknowledgment (SACK) trong RFC 2018, cho phép bên nhận báo cáo chính xác từng dải dữ liệu gián đoạn đã nhận thành công để bên gửi chỉ truyền lại đúng các phân đoạn còn thiếu.

Tuy nhiên, việc triển khai một đặc tả giao thức vào kiến trúc nhân hệ điều hành thực tế luôn tiềm ẩn rủi ro. Vào giữa năm 2019, các nhà nghiên cứu an ninh mạng đã công bố cụm lỗ hổng nghiêm trọng trong ngăn xếp mạng của nhân Linux, tiêu biểu là CVE-2019-11477 (thường gọi là SACK Panic). Bằng cách khai thác cơ chế quản lý hàng đợi bộ đệm socket (sk_buff) khi kết hợp kích thước phân đoạn tối đa (MSS) cực nhỏ với các khối SACK giả mạo, kẻ tấn công từ xa có thể kích hoạt lỗi tràn số nguyên 16-bit khiến máy chủ sập nguồn ngay lập tức.

Việc phân tích cặn kẽ lỗ hổng SACK Panic giúp các kỹ sư hiểu rõ cách thức phân bổ bộ nhớ nhân, sự nguy hiểm của các phân đoạn vi mô và lý do kiến trúc đóng gói Lớp 3 của RiceGuard đóng vai trò phòng vệ cốt lõi trước các đòn tấn công phân mảnh tầng vận chuyển.

+-------------------------------------------------------------------------+
|              SO SÁNH CƠ CHẾ: ACK LŨY KẾ ĐỐI CHIẾU SELECTIVE ACK (RFC 2018)|
+-------------------------------------------------------------------------+
| TÌNH HUỐNG: Gói tin 1, 2, 4, 5 đến đích an toàn; Gói tin 3 bị rớt mạng. |
|                                                                         |
| 1. RFC 793 ACK LŨY KẾ (Cumulative ACK):                                 |
|    Bên gửi ──[Gói 1]─[Gói 2]─[Gói 3 (MẤT)]─[Gói 4]─[Gói 5]──> Bên nhận  |
|    Bên nhận gửi ACK=3. Bên gửi không biết gói 4 và 5 đã tới nơi chưa.   |
|    Kết quả: Bên gửi buộc phải truyền lại Gói 3, Gói 4 và Gói 5.         |
|                                                                         |
| 2. RFC 2018 XÁC NHẬN CHỌN LỌC (SACK Option):                            |
|    Bên gửi ──[Gói 1]─[Gói 2]─[Gói 3 (MẤT)]─[Gói 4]─[Gói 5]──> Bên nhận  |
|    Bên nhận gửi ACK=3 kèm khối SACK [Biên: 4 đến 6].                    |
|    Kết quả: Bên gửi CHỈ truyền lại Gói 3. Hoàn toàn không phí băng thông.|
+-------------------------------------------------------------------------+

Tiến trình tiến hóa của cơ chế xác nhận TCP: ACK lũy kế đối chiếu SACK (RFC 2018)

Trong mô hình xác nhận lũy kế cổ điển, trường số thứ tự xác nhận trong tiêu đề TCP 20 byte chỉ chỉ định số thứ tự tiếp theo mà bên nhận đang đợi, ngầm xác nhận rằng mọi byte dữ liệu trước đó đều đã được xử lý toàn vẹn. Khi nhiều phân đoạn rời rạc bị rớt do tắc nghẽn bộ đệm router, bên gửi rơi vào tình thế nan giải: hoặc truyền lại phân đoạn đầu tiên rồi chờ một chu kỳ RTT để biết gói tiếp theo, hoặc truyền lại mù quáng toàn bộ cửa sổ.

Đặc tả RFC 2018 đã tháo gỡ điểm nghẽn này bằng cách tích hợp tùy chọn SACK vào phần tùy chọn mở rộng của tiêu đề TCP (Option Kind 5). Bên nhận có thể đính kèm tối đa bốn khối dữ liệu gián đoạn đã nhận được. Mỗi khối được mô tả bằng hai số nguyên không dấu 32-bit:

  • Biên trái (Left Edge): Số thứ tự chuỗi đầu tiên của khối dữ liệu rời rạc đã nhận.
  • Biên phải (Right Edge): Số thứ tự chuỗi liền kề ngay sau byte cuối cùng của khối đó.

Dựa vào các chỉ số biên này, bên gửi duy trì đường truyền liên tục và chỉ tái gửi đúng những dải byte bị khuyết. SACK biến quy trình khôi phục mất gói thành một thao tác chuẩn xác, trở thành nền tảng quan trọng của Internet băng thông rộng.

Cấu trúc tùy chọn SACK: Định dạng khối và mô tả biên chuỗi

Tiêu đề TCP giới hạn tối đa 40 byte cho toàn bộ các tham số tùy chọn. Tùy chọn SACK cơ bản sử dụng 2 byte đầu tiên cho mục đích định danh: Loại tùy chọn (Kind = 5, 1 byte) và Chiều dài (Length, 1 byte). Mỗi khối SACK tiếp theo tiêu tốn 8 byte (hai chỉ số biên 32-bit).

Do các kết nối TCP hiện đại hầu hết đều bật tính năng TCP Timestamps (RFC 7323) chiếm 10 byte, không gian còn lại cho SACK bị co hẹp:

  • Khi không có Timestamps: 40 byte - 2 byte = 38 byte trống, chứa tối đa 4 khối SACK (sử dụng 32 byte).
  • Khi có Timestamps: 40 byte - 10 byte (Timestamps) - 2 byte = 28 byte trống, chứa tối đa 3 khối SACK (sử dụng 24 byte).

Khi kết hợp cùng Duplicate SACK (D-SACK, RFC 2883), khối đầu tiên có thể thông báo phân đoạn bị nhận lặp, giúp bên gửi nhận biết hiện tượng gói tin bị nhân bản hoặc vượt ngưỡng thời gian chờ ảo. Dù cấu trúc byte rất tường minh, việc theo dõi các dải số thứ tự biến thiên này đòi hỏi các cấu trúc dữ liệu phức tạp trong nhân hệ điều hành.

Cách nhân Linux quản lý các phân đoạn mất thứ tự: Hàng đợi sk_buff Queue

Trong nhân Linux, mọi gói tin luân chuyển đều được đóng gói trong một cấu trúc dữ liệu cốt lõi gọi là bộ đệm socket, hay struct sk_buff (skb). Đối với mỗi socket TCP hoạt động, nhân duy trì một hàng đợi truyền và truyền lại, liên kết các đối tượng sk_buff này thành một danh sách liên kết đôi.

Khi các giao diện mạng tốc độ cao kích hoạt tính năng Generic Segmentation Offload (GSO) hoặc Large Send Offload (LSO), nhân không tạo các gói tin 1.500 byte đơn lẻ. Thay vào đó, nó xây dựng các siêu gói tin (super-packets) có kích thước lên tới 64 KB và bàn giao cho phần cứng card mạng phân tách thành các khung MTU ngay trước khi truyền ra dây dẫn vật lý.

Cơ chế phân mảnh bộ đệm Socket (skb) và dấu ấn tiêu thụ bộ nhớ

Để theo dõi một bộ đệm GSO tương ứng với bao nhiêu phân đoạn vật lý thực tế, nhân lưu trữ một biến đếm trong khối thông tin chia sẻ (skb_shared_info), định danh là gso_segs:

struct skb_shared_info {
    __u16 gso_size;
    __u16 gso_segs;
    struct sk_buff *frag_list;
};

Điểm mấu chốt nằm ở chỗ: gso_segs được khai báo dưới dạng số nguyên không dấu 16-bit (__u16), có giá trị cực đại là 65.535.

Khi nhận được gói tin mang khối SACK chỉ xác nhận một phần nhỏ bên trong một siêu gói tin GSO đang bay trên đường truyền, nhân không thể giải phóng toàn bộ vùng nhớ. Thay vào đó, nó phải gọi hàm tcp_fragment() để chẻ nhỏ đối tượng sk_buff thành hai phần riêng biệt: một phần đã nhận và một phần tiếp tục chờ xác nhận.

+-------------------------------------------------------------------------+
|                  CƠ CHẾ CHẺ NHỎ BỘ ĐỆM SK_BUFF TRONG NHÂN LINUX          |
+-------------------------------------------------------------------------+
| 1. Bộ đệm GSO (32 KB): Dữ liệu: 32 KB | gso_segs: 23 Phân đoạn           |
| 2. Gói SACK tới:       Nhân gọi tcp_fragment() tách bộ đệm              |
| 3. Mẫu tấn công:       Micro-MSS (48 byte) ép hàng đợi phân mảnh        |
|                        thành hàng ngàn struct sk_buff -> TRÀN gso_segs  |
+-------------------------------------------------------------------------+

Cơ chế lỗ hổng SACK Panic (CVE-2019-11477): Tràn số nguyên 16-bit

Bản chất của lỗ hổng CVE-2019-11477 phát sinh từ cách nhân Linux gộp các phân đoạn nhỏ trong quá trình xử lý hàng đợi truyền lại.

Nhằm tối ưu hóa hiệu năng truyền dẫn, nhân cố gắng gộp các phân đoạn liền kề thông qua hàm tcp_shift_skb_data(). Quá trình này dồn các số thứ tự dữ liệu vào một bộ đệm sk_buff đứng đầu và tăng biến đếm gso_segs tương ứng với số phân đoạn vừa được sáp nhập:

/* net/ipv4/tcp_input.c */
tcp_skb_pcount_add(prev, pcount);

Hàm tcp_skb_pcount_add thực hiện phép cộng skb_shinfo(skb)->gso_segs += pcount.

Vì trường gso_segs chỉ có kích thước 16-bit, khi giá trị tích lũy vượt quá ngưỡng 65.535, hiện tượng tràn số nguyên xảy ra, khiến biến đếm quay vòng về giá trị 0.

Ngay sau đó, ngăn xếp mạng của nhân thực hiện phép kiểm tra an toàn trong hàm tcp_fragment():

BUG_ON(tcp_skb_pcount(skb) == 0);

Chỉ thị BUG_ON() là cơ chế phòng vệ tối hậu của nhân hệ điều hành: khi điều kiện này trả về đúng, hệ thống lập tức đình chỉ mọi hoạt động để ngăn chặn hỏng bộ nhớ, dẫn đến hiện tượng sụp đổ toàn bộ hệ thống (Kernel Panic). Kẻ tấn công từ xa hoàn toàn có thể kích hoạt thảm họa này chỉ trong vài giây thông qua một kết nối TCP thông thường.

Vai trò của thông số Micro-MSS trong việc khuếch đại cạn kiệt bộ đệm nhân

Làm cách nào kẻ tấn công có thể đẩy gso_segs vượt qua con số 65.535 mà không cần gửi hàng trăm megabyte dữ liệu? Bí mật nằm ở việc thao túng tham số Maximum Segment Size (MSS) trong quá trình bắt tay ba bước.

Thông thường, giá trị MSS dao động từ 1.400 đến 1.460 byte. Tuy nhiên, RFC 793 cho phép máy khách quảng bá giá trị MSS rất nhỏ. Ngưỡng thấp nhất mà nhân Linux thời điểm đó chấp nhận là 48 byte (vừa đủ cho tiêu đề IPv4 và tiêu đề TCP cơ bản).

Với MSS bằng 48 byte, mỗi phân đoạn chỉ mang theo vài byte dữ liệu thực tế. Một bộ đệm 32 KB đơn lẻ trong hàng đợi sẽ được tính toán:

$$ ext{gso_segs} = rac{32.768 ext{ byte}}{48 ext{ byte/phân đoạn}} pprox 682 ext{ phân đoạn}$$

Bằng cách gửi chuỗi gói tin SACK xác nhận cách quãng các đoạn 48 byte xen kẽ, kẻ tấn công buộc nhân Linux phải chẻ hàng đợi thành hàng ngàn phân đoạn sk_buff siêu nhỏ. Khi tcp_shift_skb_data() tìm cách gộp các mảnh vi mô này, số lượng phân đoạn nhanh chóng vượt ngưỡng 65.535, gây sập hệ thống ngay tức khắc.

Hiện tượng SACK Sluggishness và cạn kiệt tài nguyên (CVE-2019-11478 & CVE-2019-11479)

Cùng thời điểm phát hiện SACK Panic, các chuyên gia an ninh mạng cũng chỉ ra hai lỗ hổng từ chối dịch vụ đi kèm:

  1. CVE-2019-11478 (SACK Sluggishness): Kẻ xấu làm phân mảnh hàng đợi gửi bằng các khối SACK. Khi phải duyệt qua các danh sách liên kết chứa hàng chục ngàn phần tử sk_buff mỗi khi xảy ra sự cố mất gói, nhân tiêu tốn toàn bộ tài nguyên CPU trong các vòng lặp duyệt, đẩy mức sử dụng CPU lên 100%.
  2. CVE-2019-11479 (Tiêu hao tài nguyên qua MSS cực thấp): Bằng việc ép máy chủ trao đổi dữ liệu với MSS 48 byte, số lượng gói tin phát sinh tăng gấp 30 lần, làm cạn kiệt bộ nhớ đệm socket của hệ thống.

Chi phí CPU bất đối xứng và gánh nặng duyệt danh sách liên kết

Tính chất bất đối xứng của cuộc tấn công là vô cùng nguy hiểm. Kẻ tấn công chỉ cần gửi đi vài kilobyte gói tin SYN và ACK. Ngược lại, máy chủ nạn nhân phải cấp phát hàng megabyte bộ nhớ nhân (kmalloc), duyệt qua các cấu trúc danh sách liên kết khổng lồ và xử lý ngắt phần mềm liên tục (ksoftirqd).

Đối với các cổng kết nối đám mây và hệ thống VPN doanh nghiệp phục vụ hàng chục ngàn kết nối đồng thời, lưu lượng SACK vi mô có thể làm tê liệt toàn bộ hạ tầng mà không kích hoạt các bộ cảnh báo DDoS lưu lượng lớn truyền thống.

Cách đường hầm Lớp 3 và RiceGuard che chắn trước các lỗ hổng SACK

Sự xuất hiện của các lỗ hổng ở tầng vận chuyển chứng minh tầm quan trọng của việc bảo vệ dữ liệu bằng các lớp phủ mã hóa độc lập. Khi các thiết bị kết nối trực tiếp qua mạng Internet công cộng, mọi thiết bị trung gian và kẻ tấn công đều có thể gửi các tùy chọn TCP độc hại để thăm dò hệ điều hành.

Kiến trúc đường hầm Lớp 3 hiện đại của VPNRice dựa trên giao thức RiceGuard giúp loại bỏ hoàn toàn bề mặt tấn công này:

  1. Tách biệt tầng vận chuyển qua UDP: RiceGuard đóng gói toàn bộ dữ liệu người dùng bên trong các gói tin UDP có độ dài cố định, được bảo vệ bằng mật mã ChaCha20-Poly1305. Các nút mạng bên ngoài không thể can thiệp hay chèn các tùy chọn TCP SACK giả mạo vào luồng truyền dẫn bên ngoài.
  2. Khống chế ngưỡng MSS tại biên gateway: Máy chủ RiceGuard áp dụng cơ chế MSS Clamping nghiêm ngặt tại ranh giới mạng. Mọi kết nối bên trong cố tình thiết lập MSS vi mô bất thường (< 536 byte) đều bị điều chỉnh hoặc từ chối, ngăn chặn triệt để nguy cơ phân mảnh bộ đệm nhân.
  3. Cách ly trạng thái socket: Đường hầm kết thúc tại các gateway được tăng cường bảo mật, bảo vệ an toàn cho thiết bị của người dùng tại các mạng Wi-Fi công cộng không tin cậy.

Người dùng trên nhiều nền tảng có thể củng cố ngăn xếp mạng của mình thông qua trang tải VPN, thiết lập các kênh truyền dẫn mã hóa an toàn nhằm triệt tiêu các nguy cơ tấn công tầng vận chuyển.

+-------------------------------------------------------------------------+
|                  CƠ CHẾ PHÒNG THỦ LỚP 3 CỦA RICEGUARD                   |
+-------------------------------------------------------------------------+
| KHÔNG BẢO VỆ:  [Kẻ tấn công] ── MSS=48 + SACK độc hại ──> [Sập nhân]    |
| CÓ BẢO VỆ:     [Kẻ tấn công] ── Gói tin vi mô ──> [Gateway RiceGuard]   |
|                                                   ├─ Khống chế MSS      |
|                                                   ├─ Lọc bỏ tùy chọn    |
|                                                   └─ Đóng gói UDP       |
|                                                   └─> [Máy chủ an toàn] |
+-------------------------------------------------------------------------+

Tăng cường bảo mật nhân và kiểm tra gói tin: Tinh chỉnh Sysctl và MSS Clamping

Quản trị viên hệ thống có thể áp dụng ngay các biện pháp kiểm soát qua cấu hình sysctl và tường lửa Linux.

1. Tạm thời vô hiệu hóa SACK (Biện pháp khẩn cấp)

Trong trường hợp chưa thể cập nhật nhân hệ điều hành, có thể tắt tính năng xử lý SACK:

# Kiểm tra trạng thái SACK hiện tại (1 = bật, 0 = tắt)
sysctl net.ipv4.tcp_sack

# Vô hiệu hóa SACK trên toàn hệ thống
sudo sysctl -w net.ipv4.tcp_sack=0

Đánh đổi: Thao tác này ngăn chặn hoàn toàn nguy cơ sập nhân, nhưng tốc độ truyền tải trên các đường truyền kém ổn định sẽ giảm do hệ thống quay về cơ chế truyền lại lũy kế.

2. Thiết lập giới hạn MSS tối thiểu

Các bản nhân Linux hiện đại bổ sung tham số tcp_min_snd_mss, ngăn chặn việc thương lượng các mức MSS quá thấp:

# Thiết lập MSS gửi tối thiểu là 536 byte (chuẩn RFC 879)
sudo sysctl -w net.ipv4.tcp_min_snd_mss=536

3. Khống chế MSS tại tường lửa Iptables

Các gateway biên nên lọc bỏ hoặc điều chỉnh các gói tin bắt tay có giá trị MSS nguy hiểm:

# Chặn các gói tin SYN có MSS nhỏ hơn 536 byte
sudo iptables -A INPUT -p tcp --tcp-flags SYN,RST SYN     -m tcpmss --mss 1:535 -j DROP

# Tự động điều chỉnh MSS đầu ra theo Path MTU
sudo iptables -t mangle -A POSTROUTING -p tcp --tcp-flags SYN,RST SYN     -j TCPMSS --clamp-mss-to-pmtu

4. Giám sát gói tin bất thường với Tcpdump

Kỹ sư an ninh mạng có thể nhận diện các nỗ lực thăm dò SACK bất thường bằng lệnh:

# Bắt các gói tin TCP SYN có MSS dưới 500 byte
sudo tcpdump -nn -i eth0 'tcp[tcpflags] & tcp-syn != 0 and tcp[20:2] < 500'

So sánh kiến trúc: TCP tiêu chuẩn đối chiếu SACK chưa vá và đường hầm bảo vệ

Bảng tổng hợp dưới đây làm rõ các đặc tính vận hành giữa các phương thức:

Chỉ số vận hành TCP chuẩn RFC 793 TCP SACK chưa vá (RFC 2018) Đường hầm RiceGuard bảo vệ
Tốc độ khôi phục mất gói Chậm (Truyền lại cả cửa sổ) Nhanh (Truyền lại chọn lọc) Tối ưu (Khôi phục tầng L3 UDP)
Nguy cơ sập nhân hệ thống Miễn nhiễm (Không có SACK) Nghiêm trọng (Lỗi tràn số 16-bit) Triệt tiêu (Nhờ UDP & MSS Clamping)
Gánh nặng CPU khi bị quét Thấp Cực lớn (Phân mảnh hàng đợi) Rất thấp (Bộ tăng tốc phần cứng)
Tiêu hao bộ nhớ RAM Hàng đợi tĩnh cố định Bùng nổ (Hàng ngàn sk_buff nhỏ) Được kiểm soát chặt chẽ
Can thiệp từ thiết bị mạng Dễ bị xem tiêu đề Dễ bị loại bỏ tùy chọn Miễn nhiễm (Mã hóa toàn vẹn)
Hiệu suất trên mạng chập chờn Giảm tới 60% băng thông Duy trì hiệu suất cao Duy trì tốc độ cao ổn định

Bản vá hiện đại trong nhân Linux: Hàng đợi cây đỏ đen RB-Tree và tối ưu hóa SACK Shift

Sự cố SACK Panic đã thúc đẩy một đợt tái cấu trúc quy mô lớn trong ngăn xếp mạng Linux do kỹ sư Eric Dumazet dẫn dắt. Bản vá chính thức bao gồm các cải tiến đồng bộ:

  1. Kiểm tra tràn biến đếm phân đoạn: Nhân bổ sung các rào chắn kiểm tra logic trong hàm tcp_shifted_skb(), đảm bảo quá trình gộp bộ đệm không bao giờ để gso_segs quay vòng về 0.
  2. Chuyển đổi danh sách liên kết sang Cây Đỏ Đen (RB-Tree): Hàng đợi xử lý gói tin mất thứ tự được chuyển từ danh sách liên kết tuyến tính sang cây đỏ đen tự cân bằng (struct rb_node). Độ phức tạp thuật toán giảm từ $O(N)$ xuống $O(\log N)$, vô hiệu hóa hoàn toàn nguy cơ nghẽn CPU của CVE-2019-11478.
  3. Cố định ngưỡng MSS tối thiểu trong mã nguồn: Nhân áp dụng ngưỡng sàn MSS cứng trong ngăn xếp TCP, ngăn chặn hàm tcp_fragment() chia nhỏ bộ đệm xuống dưới 48 byte trong mọi tình huống.

Những cải tiến kiến trúc này cho phép cơ chế SACK duy trì khả năng tăng tốc mạng vượt trội mà không gây nguy hiểm cho bộ nhớ nhân.

Danh mục kiểm tra hạ tầng TCP SACK an toàn cho kỹ sư mạng

Để bảo đảm hệ thống máy chủ và hạ tầng kết nối luôn an toàn trước các đòn tấn công khai thác tầng vận chuyển, các kỹ sư cần thực hiện:

  1. Rà soát phiên bản nhân hệ điều hành: Đảm bảo tất cả máy chủ Linux sản xuất chạy phiên bản nhân đã vá (Linux 4.19.52+, 5.1.11+ hoặc các bản cập nhật LTS từ Debian, Ubuntu, RHEL).
  2. Khống chế giá trị MSS tại tường lửa biên: Cấu hình các bộ định tuyến biên và cân bằng tải để điều chỉnh hoặc từ chối mọi kết nối có MSS dưới 536 byte.
  3. Kích hoạt các tham số Sysctl phòng thủ: Xác nhận net.ipv4.tcp_min_snd_mss đã được gán giá trị 536 trên tất cả máy chủ công cộng.
  4. Cô lập lưu lượng làm việc từ xa: Áp dụng đường hầm VPN RiceGuard cho các kỹ sư làm việc từ xa nhằm chấm dứt các kết nối TCP trực tiếp tại gateway an toàn.
  5. Giám sát tải xử lý SoftIRQ: Thiết lập cảnh báo Prometheus khi CPU tiêu tốn thời gian bất thường cho tiến trình ksoftirqd, dấu hiệu của các đợt tấn công duyệt hàng đợi.
  6. Bật cơ chế phòng thủ SYN-Cookie: Kết hợp giải pháp SACK với net.ipv4.tcp_syncookies=1 để chống cạn kiệt tài nguyên phiên ngay từ bước bắt tay.

Sự phối hợp chặt chẽ giữa việc tăng cường bảo mật nhân hệ điều hành và áp dụng đường hầm mã hóa Lớp 3 cho phép các tổ chức duy trì tốc độ truyền dẫn đỉnh cao mà vẫn loại trừ triệt để nguy cơ từ các lỗ hổng cạn kiệt bộ nhớ mạng.