Tấn công ROME: Khi trọng số mô hình AI bị sửa đổi ngay trong bộ nhớ GPU mà không để lại dấu vết trên ổ đĩa

AI & ML19 tháng 10, 2026·10 phút đọc

Tấn công ROME (Rank-One Model Editing) cho phép một module Python độc hại viết lại trọng số mô hình trực tiếp trong bộ nhớ GPU, khiến AI trả lời sai lệch mà không thay đổi một byte nào trên ổ đĩa. Các công cụ bảo mật truyền thống như EDR hoàn toàn bỏ sót lớp tấn công này. Stealthium đã trình diễn cách phát hiện thông qua việc liên kết mỗi lần khởi chạy nhân CUDA với đoạn mã Python đã gọi nó, cùng hướng nghiên cứu mới về theo dõi thay đổi trọng số kèm ngăn xếp lệnh gọi.

Tấn công ROME: Khi trọng số mô hình AI bị sửa đổi ngay trong bộ nhớ GPU mà không để lại dấu vết trên ổ đĩa

Một mô hình AI hoàn toàn có thể bị "đầu độc" về một chủ đề cụ thể mà không cần thay đổi một byte dữ liệu nào trên đĩa. Không có tệp tin nào để quét, không có tiến trình lạ nào để bắt giữ, không có chữ ký mã độc nào để đối chiếu. Với mọi hệ thống giám sát, đó vẫn chỉ là một mô hình đang làm đúng việc của mô hình.

Đây là bản chất của tấn công ROME (Rank-One Model Editing) — một kỹ thuật chỉnh sửa trọng số mô hình trực tiếp trong bộ nhớ GPU. Bài viết này phân tích cách thức tấn công vận hành, tại sao các công cụ bảo mật truyền thống bỏ sót nó, và cách một nền tảng bảo mật runtime phát hiện ra dấu hiệu bất thường.

Tấn công ROME hoạt động như thế nào?

Năm 2022, Meng và cộng sự giới thiệu ROME — kỹ thuật thực hiện một chỉnh sửa nhỏ, có chủ đích vào một ma trận trọng số ở tầng giữa của mô hình. Nói một cách đơn giản: mô hình là một khối số khổng lồ gọi là trọng số (weights), toàn bộ quá trình suy luận là một phép nhân lớn. Chỉ cần thay đổi vài con số trong đó, câu trả lời của mô hình sẽ thay đổi theo.

Kỹ thuật này chính là nền tảng của PoisonGPT do Mithril Security thực hiện năm 2023 — một mô hình ngôn ngữ bị "gọt não" được đăng công khai trên Hugging Face để lan truyền thông tin sai lệch.

Điều đáng lo ngại nằm ở chỗ: trọng số bị vá trực tiếp trong bộ nhớ GPU. Không truy cập tệp tin, không thêm lệnh ioctl, thậm chí không có cả mmap. Thao tác chỉnh sửa là công việc CUDA thông thường, được phát ra từ chính tiến trình python3 đang chạy suy luận, trên cùng một ngữ cảnh (context). Bất kỳ công cụ nào theo dõi tệp tin, socket hay driver đều chỉ thấy một tác vụ suy luận bình thường.

Đây là kiểu tấn công không bao giờ rời khỏi GPU. Một thư viện bị nhiễm độc yêu cầu GPU làm thêm vài phép toán và ghi kết quả đè lên một vài con số của mô hình. Những yêu cầu tương tự được gửi đến mô hình hàng nghìn lần trong lúc trả lời một câu hỏi. Không mở gì, không khởi chạy gì, không gửi gì ra mạng. Vậy mà hành vi của mô hình đã bị thao túng hoàn toàn.

Vì sao EDR và các công cụ bảo mật truyền thống "mù" trước lớp tấn công này?

Các giải pháp giám sát và bảo mật quen thuộc — EDR trên máy chủ hay ADR móc vào ứng dụng — được thiết kế để theo dõi hoạt động tiến trình: mở tệp, sinh tiến trình con, giao tiếp qua mạng. Tấn công ROME không có bất kỳ hành vi nào trong số đó.

Phần lớn khối lượng công việc GPU chạy dưới trình thông dịch Python: các job huấn luyện PyTorch, máy chủ vLLM và SGLang, quy trình ComfyUI, thử nghiệm JAX, notebook CuPy. Python điều phối, mã biên dịch chạy trên thiết bị, và từ góc nhìn máy chủ tất cả đều trông giống nhau: một tiến trình python3 đang dùng GPU.

Điều này tạo ra lỗ hổng phát hiện nghiêm trọng, bởi đoạn mã quyết định thứ gì sẽ chạy không phải lúc nào cũng là một tệp thực thi độc lập để băm và ký số. Nó đến dưới dạng một gói bạn import, một tệp mô hình bạn nạp, một chuỗi bạn đánh giá lúc runtime, hoặc một module CUDA được dựng trong bộ nhớ rồi giao cho driver. Tính linh hoạt của Python là ưu điểm lớn cho công việc AI — nhưng mỗi cánh cửa đó cũng là một con đường xâm nhập.

Có thể kể đến vài con đường điển hình:

  • Import chạy mã: Việc import một gói sẽ thực thi mã khởi tạo của nó. Trong vụ gói PyPI của lightning bị xâm phạm tháng 4/2026 (CVE-2026-44484), các phiên bản 2.6.2 và 2.6.3 kích hoạt payload đánh cắp thông tin xác thực ngay khi import.
  • Tệp khởi động chạy trước ứng dụng: Một tệp .pth trong site-packages có thể mang theo dòng import thực thi được. Vụ LiteLLM tháng 3/2026 lợi dụng điều này để khởi động trình đánh cắp thông tin ngay khi Python khởi chạy.
  • Tệp mô hình có thể thực thi mã: Quá trình unpickle có thể chạy mã tùy ý. Đã có những tệp mô hình trên Hugging Face mở reverse shell hoặc gọi về máy chủ điều khiển khi được nạp. Định dạng như safetensors tránh được việc thực thi pickle, nhưng rất nhiều mô hình vẫn được phân phối dưới dạng pickle.
  • Chuỗi có thể biến thành mã: exec() và eval() chạy chuỗi Python, trong khi Triton và CuPy có thể biên dịch nhân GPU lúc runtime. Trong một vụ Sysdig điều tra, một plugin Open WebUI giấu payload sau 64 lớp giải mã và exec(), mã Python được giải mã chạy trong bộ nhớ, không bao giờ chạm vào đĩa.

Góc nhìn từ phía GPU: nhật ký kernel và ngăn xếp lệnh gọi

Một lần khởi chạy CUDA (CUDA launch) là yêu cầu từ phía CPU để GPU chạy một nhân (kernel) — một chương trình nhỏ thực hiện công việc song song trên dữ liệu GPU. Lệnh khởi chạy cung cấp đầu vào cho nhân và cách chia công việc trên các luồng GPU; nó thường trả về trước khi GPU xử lý xong, để CPU tiếp tục làm việc trong lúc GPU chạy.

Với tấn công ROME, từ góc nhìn GPU, đó là suy luận tiêu chuẩn cộng thêm một loạt lần khởi chạy nhân CUDA phụ trội. Các nhân suy luận trông giống hệt nhau giữa một câu hỏi thông thường và một câu hỏi bị ROME can thiệp — nhưng ngăn xếp lệnh gọi Python phía sau thì không.

Cụ thể, khi một nền tảng như Stealthium ghi lại hoạt động, cùng một nhân có thể xuất hiện với hai ngăn xếp khác nhau:

  • Ngăn xếp kết thúc bằng SpaceLM.reply — đây là câu hỏi của người dùng.
  • Ngăn xếp kết thúc bằng _keys trong rome/calibration.py — đây là ROME chạy chính mô hình để truy vết.

Phần còn lại của quá trình chỉnh sửa khởi chạy những nhân chưa từng xuất hiện trong một lần chạy sạch: thu thập các key đã bắt, giải hệ phương trình tuyến tính để tính trọng số mục tiêu, rồi ghi chúng đè lên trọng số đang sống trong bộ nhớ GPU. Đây là bất thường thứ hai mà hệ thống giám sát có thể cảnh báo.

calibration.py::_calibrate đã chạy suy luận, giải một hệ phương trình tuyến tính và ghi vào bộ nhớ GPU — tất cả bên trong một tiến trình mà mọi người đều tin tưởng. Đây không còn là chuyện "một khối lượng công việc Python đáng tin đã dùng GPU".

Vượt ra ngoài CUDA: quan sát tầng framework

CUDA chỉ là một lớp. Bên cạnh khả năng quan sát toàn hệ thống, CUDA và GPU trực tiếp, các giải pháp hiện đại còn theo dõi những điểm vào của trình thông dịch Python:

  • PythonImport ghi lại mã Python nào được import, đường dẫn tệp mà nó phân giải thành, và ai đã import nó. Điều này phân biệt được module secrets chuẩn của thư viện với một tệp secrets.py cục bộ cùng tên.
  • PythonExec ghi lại các lệnh exec, eval và compile lúc runtime kèm ngăn xếp lệnh gọi, cho thấy đoạn mã đã được giải mã mà một loader dựa trên chuỗi thực sự đã chạy.

Phát hiện mọi thao tác monkey-patch của Python là điều khả thi về nguyên tắc, nhưng có thể tạo ra chi phí đáng kể cho một số khối lượng công việc và sinh ra cảnh báo nhiễu cho những hành vi vô hại trong thư viện chuẩn. Thay vào đó, cách tiếp cận thực tế là chú thích ngăn xếp lệnh gọi Python lên các sự kiện hoạt động CUDA — giúp chuyên viên phân tích phân biệt hành vi hợp pháp với bất thường.

Hướng nghiên cứu tiếp theo: chính xác trọng số nào đã thay đổi?

Một lần khởi chạy nhân, xét riêng nó, không thể trả lời câu hỏi trọng số nào đã bị sửa. Các tham số của nó chỉ là con trỏ thiết bị thô, và cùng một nhân dùng để cộng hai tensor khi xử lý prompt cũng có thể dùng để cộng một giá trị hiệu chỉnh vào ma trận trọng số.

Để có góc nhìn sâu hơn, hướng nghiên cứu mới tập trung vào tín hiệu ở tầng framework: quan sát nơi PyTorch (hoặc framework khác) ghi nhận một thay đổi tensor tại chỗ, và thu giữ ngăn xếp lệnh gọi Python tại đúng thời điểm đó. Kết quả là một tín hiệu bổ sung: sự kiện thay đổi trọng số gắn với tensor, đường dẫn mã và tiến trình cụ thể.

Sự khác biệt then chốt nằm ở ngăn xếp lệnh gọi — cùng một hành động tensor_write nhưng ý nghĩa hoàn toàn khác nhau:

  • Khi mô hình nạp lên, ngăn xếp đi qua torch.load, _load, Unpickler.load, _rebuild_tensor — đây là loader đang nạp trọng số đúng như thiết kế.
  • Khi ROME chỉnh sửa, ngăn xếp đi qua SpaceLM.forward, MultiheadAttention.forward, _calibrate, _eval — một tệp không có lý do gì để sửa trọng số.

Nguyên tắc phát hiện rất rõ ràng: báo động khi trọng số thay đổi từ một đường dẫn không có loader phía sau. Đây cũng chính là nguyên lý mà EDR dùng để gắn cờ một tiến trình đọc tệp thông tin xác thực từ shell thay vì từ thư viện xác thực: hành động là bình thường, kẻ gọi mới là manh mối.

Đáng chú ý là cả hai góc nhìn độc lập — một từ phía công việc GPU, một từ phía sổ sách của chính framework — đều chỉ về cùng một hàm _calibrate.

Ý nghĩa đối với bảo mật AI tại Việt Nam

Sự phụ thuộc ngày càng tăng vào mô hình AI mã nguồn mở — phần lớn tải về từ Hugging Face hoặc các kho công khai — đặt ra câu hỏi thực tế cho các đội ngũ kỹ thuật trong nước: liệu họ có đang tin tưởng tuyệt đối vào một tệp mô hình chỉ vì nó không có chữ ký mã độc?

Bài học cốt lõi từ nghiên cứu này có thể tóm gọn: suy luận AI không còn là hộp đen miễn nhiễm với bảo mật. Khi xây dựng hệ thống chạy mô hình trong môi trường sản xuất — dù là API nội bộ cho doanh nghiệp hay dịch vụ cho người dùng cuối — cần tính đến việc giám sát ở nhiều lớp: từ tệp tin và gói phụ thuộc, qua tiến trình Python, cho đến hoạt động nhân GPU và bản thân trọng số mô hình.

ROME là một ví dụ cụ thể để thấy rõ cả giá trị lẫn giới hạn của khả năng quan sát runtime GPU. Nó cho thấy hoạt động nhân cùng ngăn xếp lệnh gọi Python có thể giúp điều tra bất thường như thế nào, và vì sao cần nhìn sâu hơn vào framework cùng hành vi mô hình để có thêm ngữ cảnh.

Việc phân biệt giữa nạp trọng số hợp pháp và chỉnh sửa về sau là một thách thức thực sự — nhưng chính những thử nghiệm minh bạch như thế này giúp cộng đồng hiểu rõ hơn cách bảo vệ các hệ thống AI.

Chia sẻ:FacebookX
Nội dung tổng hợp bằng AI, mang tính tham khảo. Xem bài gốc ↗