CVE-2026-98432: Vì sao kfree_rcu() vá lỗi race TCP Linux

Phân tích CVE-2026-98432: vì sao luồng đọc RCU không khóa trong TCP IPv4 chạm vào IP options đã giải phóng, cách kfree_rcu() vá và cách giảm thiểu rủi ro.

CVE-2026-98432: Vì sao kfree_rcu() vá lỗi race TCP Linux
Trong bài viết này

CVE-2026-98432 là lỗi vòng đời bộ nhớ chứ không phải lỗi phân tích gói tin. Ngăn xếp TCP IPv4 cho phép luồng đọc RCU không khóa dùng bộ đệm IP options, trong khi hai hàm hủy lại giải phóng bộ đệm đó bằng kfree() thông thường. Bản vá upstream đổi lệnh gọi này sang kfree_rcu(). Hiểu vì sao cách này hiệu quả cũng giúp quản trị viên biết nên tắt gì trong lúc chờ kernel đã vá.

Những gì đã được công bố

Lỗ hổng xuất hiện trên NVD ngày 15/10/2026. Nhóm CVE của nhân Linux chấm 9.8 theo CVSS 3.1: khai thác được qua mạng, không cần đặc quyền và không cần người dùng tương tác. Lỗi được xếp loại race condition (CWE-362) dẫn tới use-after-free (CWE-416).

Các nhà phân phối chấm thấp hơn. Red Hat và SUSE chấm 8.1, còn Amazon Linux chấm từ 7.0 đến 8.1. Khoảng chênh này phản ánh việc khai thác thực tế phụ thuộc nhiều vào thời điểm và cấu hình.

Lỗi bắt nguồn từ commit 079096f103fa ("tcp/dccp: install syn_recv requests into ehash table") có trong Linux 4.4. Vì vậy, phần lớn kernel từ 4.4 đến các nhánh 6.x hiện nay đều bị ảnh hưởng.

Cú bàn giao con trỏ phá vỡ giao kèo

Bánh răng định thời bằng đồng và thép đặt cạnh khung bộ chuyển mạch mạng bằng nhôm trên bàn gỗ óc chó

Từ bản 4.4, request socket đang bắt tay dở được đưa vào bảng băm ehash. Nhờ vậy, kernel xử lý gói tin của chúng mà không cần khóa của socket lắng nghe. Thay vào đó, luồng đọc tự bảo vệ bằng rcu_read_lock(). RCU rất rẻ nhưng đi kèm một luật: không đoạn mã nào được trả đối tượng dùng chung về bộ cấp phát khi vẫn còn luồng đọc có thể giữ con trỏ tới nó.

Khi bắt tay hoàn tất, tcp_v4_syn_recv_sock() tạo socket con. Hàm này chuyển con trỏ IP options sang socket con thay vì sao chép bộ đệm:

newinet->inet_opt = ireq->ireq_opt;

Xét hai CPU cùng xử lý một kết nối:

  1. CPU 1 xử lý một gói SYN truyền lại trong tcp_check_req(). Dưới rcu_read_lock(), nó đi vào tcp_v4_send_synack(), nơi ip_options_build() đọc opt->optlen nhiều hơn một lần.
  2. CPU 0 hủy socket con vừa tạo, chẳng hạn khi bắt tay thất bại. Socket con TCP không có cờ SOCK_RCU_FREE, nên inet_sock_destruct() gọi kfree() ngay lập tức.
  3. Đối tượng slab bị trả về bộ cấp phát trong lúc CPU 1 đang ở giữa hai lần đọc. Lần đọc thứ hai chạm vào vùng nhớ đã giải phóng, có thể đã được cấp cho cấu trúc khác.

Bánh răng định thời bằng đồng và thép đặt cạnh khung bộ chuyển mạch mạng bằng nhôm trên bàn gỗ óc chó

Nhánh lỗi thứ hai nằm ở cơ chế di trú request socket. Cơ chế này được bật bằng net.ipv4.tcp_migrate_req=1 hoặc bằng chương trình eBPF dùng BPF_SK_REUSEPORT_SELECT_OR_MIGRATE. Ở nhánh này, reqsk_timer_handler() nhân bản request socket bằng inet_reqsk_clone(), và bản sao dùng chung ireq_opt với socket gốc. Khi bản sao được giải phóng qua tcp_v4_reqsk_destructor(), mã cũ lại gọi kfree() đồng bộ. Đúng lúc đó, một CPU khác vẫn có thể đang đọc options qua socket gốc.

IPv6 tránh được nhánh chính vì tcp_v6_syn_recv_sock() sao chép options thay vì dùng chung con trỏ. Ngày 27/10, Anthropic công bố nghiên cứu ghi nhận Xinyang Ge. Nghiên cứu mô tả listener Multipath TCP là cách khiến cuộc đua dễ xảy ra hơn nhiều, vì MPTCP tạo ra nhiều lượt bắt tay và di trú song song.

Vì sao kfree_rcu() là bản vá đúng

Bản vá upstream của Eric Dumazet, "ipv4: free inet_opt and ireq_opt after an RCU grace period", chỉ sửa đúng hai hàm hủy:

- kfree(rcu_dereference_protected(inet->inet_opt, 1));
+ kfree_rcu(rcu_dereference_protected(inet->inet_opt, 1), rcu);

Thay đổi tương tự được áp dụng cho ireq->ireq_opt trong tcp_v4_reqsk_destructor().

kfree_rcu() không giải phóng bộ nhớ ngay tại chỗ. Nó xếp đối tượng vào hàng đợi cho tới khi mọi CPU đều đi qua một quiescent state. Khi đó, mọi vùng rcu_read_lock() tồn tại lúc yêu cầu giải phóng đã kết thúc. ip_options_build() trên CPU 1 đã xong việc, và không còn con trỏ hợp lệ nào trỏ tới bộ đệm.

Các phương án khác đều tệ hơn:

  • Thêm khóa vào đường xử lý SYN sẽ xóa bỏ khả năng mở rộng có được từ bản 4.4.
  • Sao chép options ở mỗi lần bắt tay sẽ tốn thêm một lần cấp phát cho mỗi kết nối, chỉ để phục vụ tính năng mà lưu lượng thông thường gần như không dùng.

Bản vá cũng dễ backport. struct ip_options_rcu vốn đã có trường rcu_head tên rcu, nên việc trì hoãn giải phóng không đòi hỏi thay đổi bố cục cấu trúc.

Bài học chung đúng cho cả code kernel lẫn userspace. Khi con trỏ được trao giữa các chủ sở hữu, đường giải phóng phải tôn trọng cơ chế đồng bộ yếu nhất mà bất kỳ luồng đọc nào đang dựa vào. Chuyển quyền sở hữu mà không sao chép thì không sao. Giải phóng như thể không tồn tại luồng đọc không khóa nào thì không được.

Quản trị viên nên làm gì ngay

Bản vá đã vào các nhánh stable được duy trì ngày 24/10/2026. Amazon Linux phát hành bản vá cùng ngày cho kernel6.18, còn Debian và Oracle Linux đã có kernel vá.

Tính đến cuối tháng 10, tình trạng ở các bản phân phối khác vẫn chưa đồng đều:

  • Ubuntu: 22.04, 24.04 và 26.04 LTS đang được xử lý.
  • RHEL 8, 9 và 10: Bị ảnh hưởng, bản vá được hoãn sang đợt cập nhật sau.
  • RHEL 7: Không bị ảnh hưởng vì không chứa thay đổi ehash.

Hãy kiểm tra trang theo dõi của bản phân phối thay vì chỉ dựa vào số phiên bản.

Trong lúc chưa thể khởi động lại vào kernel đã vá, hãy thu hẹp bề mặt tấn công. Trước hết, tắt MPTCP và cơ chế di trú request:

# Tắt MPTCP và di trú request nếu không thực sự cần
sysctl -w net.mptcp.enabled=0
sysctl -w net.ipv4.tcp_migrate_req=0

Lưu cả hai thiết lập vào một tệp trong /etc/sysctl.d/ để chúng còn hiệu lực sau khi khởi động lại. Tắt di trú có thể ảnh hưởng tới việc chuyển giao listener không gián đoạn, nên hãy xác nhận trước rằng không dịch vụ nào phụ thuộc vào nó.

Tiếp theo, chặn các gói SYN đến có mang IPv4 options. TCP thông thường trên Internet hầu như không dùng các option như LSRR, SSRR hay Record Route. Chặn các gói này đồng nghĩa với việc kernel không bao giờ cấp phát bộ đệm dễ tổn thương cho máy ngoài không tin cậy. Trong nftables, header IPv4 dài hơn năm từ 32-bit nghĩa là gói tin có options:

ip hdrlength > 5 tcp flags & (syn | ack) == syn drop

Nếu bản iptables của bạn có match ipv4options, lệnh iptables -A INPUT -p tcp --syn -m ipv4options --any-opt -j DROP cho kết quả tương tự.

Các biện pháp này chỉ thu hẹp mức phơi nhiễm, không sửa được lỗi vòng đời. Hãy cài kernel của nhà cung cấp, khởi động lại và chạy uname -r để xác nhận kernel đang chạy là bản đã vá.

Nguồn tham khảo

  1. nvd.nist.gov
  2. ibm.com
  3. radar.offseq.com
  4. thehackernews.com
  5. explore.alas.aws.amazon.com
  6. ubuntu.com

Có hỗ trợ AI · PKN duyệt lại

0

Phản hồi

Đang tải bình luận…