Trusting Trust: source sạch, binary vẫn có thể nhiễm độc
Trusting Trust qua GNU strip: phân tích PoC NixOS, phản biện phạm vi DDC và bootstrap để hiểu vì sao source sạch chưa đủ bảo đảm binary bạn chạy cũng sạch.
Có một khoảng giữa lần cuối bạn review source và lần đầu người dùng chạy chương trình. Trong khoảng đó, nhiều chương trình khác được phép đọc, biến đổi và đóng gói những gì bạn vừa duyệt.
Mình thường chú ý đến compiler khi nghĩ về khoảng này. Trong paper Trusting-Trust Attack against an Entire Linux Distribution through Binary Manipulation, họ đã đẩy việc tấn công thông qua một công cụ ít nổi bật hơn: strip.
Mình rất thích phần triển khai của nghiên cứu, nhưng có những kết luận vẫn hơi dở. Một PoC hoạt động được đủ để xem xét cơ chế tấn công; đánh giá tính mới, hiệu quả phòng vệ và phạm vi ảnh hưởng còn cần đối chiếu với tài liệu và cấu hình cụ thể.
Điểm cần giữ: review source, chạy test và build lại đều giúp kiểm tra phần mềm, nhưng mỗi việc trả lời một câu hỏi khác nhau. Bạn cần biết mình đang kiểm chứng điều gì và vẫn phải tin vào công cụ nào.
Ai build công cụ dùng để build công cụ?
Compiler cũng là một chương trình. Muốn build compiler từ source, bạn cần một công cụ đã chạy được trước đó. Vì vậy, câu "mình tự build hết" vẫn cần một điểm bắt đầu.
Trong bài viết năm 1984, Ken Thompson mô tả một compiler đã bị sửa để cấy backdoor vào chương trình đích. Nó còn nhận ra lúc đang biên dịch compiler kế tiếp và truyền logic cấy backdoor sang bản mới. Sau đó, người kiểm tra có thể thấy source compiler sạch trong khi executable vẫn chứa hành vi cũ.
Thompson cũng nói phạm vi ý tưởng có thể mở sang assembler, loader và microcode. Đọc ông như thể chỉ cảnh báo về compiler sẽ bỏ mất phần đó. Bài gốc, bản lưu tại University of Waterloo
Bạn có thể hình dung một xưởng chế tạo máy. Xưởng có bản thiết kế đúng, nhưng chiếc máy đang dùng để chế tạo thế hệ máy tiếp theo lại âm thầm thêm một bộ phận. Thợ thay bản thiết kế không đụng tới chiếc máy đang làm việc. Đến khi bỏ máy cũ, xưởng đã nhận về một máy mới mang cùng thay đổi.
Ví dụ này giúp mình tách hai việc hay bị nhập làm một: thay tài liệu hướng dẫn sản xuất và thay những thứ đang thực hiện việc sản xuất. Chỉ làm việc đầu chưa giải thích được điều gì xảy ra với việc sau.
strip có mặt ở đó để làm gì?
GNU strip thuộc bộ binutils. Công cụ này loại bỏ symbol hoặc thông tin khác khỏi object file; tùy tùy chọn, bạn có thể giữ hoặc bỏ những phần khác nhau. Nó làm việc với file đã qua biên dịch và có thể ghi kết quả trở lại file đầu vào. GNU Binutils manual
Từ góc nhìn thiết kế quyền hạn, đây là một vị trí cần chú ý: một công cụ hậu xử lý được phép thay đổi chính sản phẩm mà bạn chuẩn bị phân phối. Gọi nó là bước "dọn dẹp" không làm quyền ghi đó nhỏ đi.
Trong một pipeline do bạn quản lý, danh sách cần review vì vậy không nên dừng ở compiler. Bạn còn cần biết ai được phép sửa executable sau đó. Đây là câu hỏi về quyền và thứ tự chạy, trước cả câu hỏi công cụ đó có tên gì.
PoC trong paper
Nhóm Julien Malka, Aman Sharma, Martin Monperrus, Stefano Zacchiroli và Théo Zimmermann giả định attacker thay được strip trong bootstrap seed, không sửa source hay recipe. Bản strip này cấy payload vào binary đầu ra, kể cả strip mới; bản mới tiếp tục truyền payload qua các thế hệ bootstrap.

Figure 4, trang 5 của paper, Malka và cộng sự, CC BY-SA 4.0. Hình gốc do tác giả minh họa, không phải log một vụ xâm nhập. Phần đánh cắp token biểu diễn tác động có thể có; payload thử nghiệm chỉ in dấu hiệu nhiễm. Phạm vi NixOS trong hình là chuỗi build được xét, không phải mọi bản cài ngoài thực tế.
Kết quả tác giả báo cáo trong v1 ngày 27/07/2026, trên x86_64-linux, revision fef9403a3e4d:
| Phép đo | Kết quả |
|---|---|
| ELF executable người dùng có thể gọi | 3.799 |
| Trong đó gọi được qua CLI | 3.791 |
| CLI executable in dấu hiệu nhiễm | 3.790 |
| Ngoại lệ CLI | Firefox bỏ qua bước strip |
| Build installer và functional tests | Không thất bại |
Payload demo in một thông báo; đây không phải báo cáo xâm nhập thực tế. Prototype có giới hạn về ELF layout, loại executable và kiến trúc; không chứng minh mọi Linux đều bị ảnh hưởng. Paper, mục 3–6

Bằng chứng từ mục 5.2, trang 7: ba vùng vàng đánh dấu số executable phát marker, lý do Firefox thoát và kết quả functional tests. Mình crop cột kết quả, giữ tiêu đề và số trang, thêm highlight; không sửa nội dung. Nguồn: Malka và cộng sự, CC BY-SA 4.0.
Một bộ test xanh đang bảo đảm điều gì?
Giả sử bạn viết test cho một công cụ chuyển đổi ảnh. Bạn đưa ảnh đầu vào, kiểm tra kích thước ảnh đầu ra, rồi so nội dung với mẫu chuẩn. Những assertion đó có thể rất tốt cho yêu cầu chức năng bạn đặt ra.
Nhưng nếu công cụ vừa tạo đúng ảnh, vừa thực hiện thêm một hành động mà test không quan sát, bạn chưa có assertion nào để kết luận về hành động ấy. Đây là khoảng cách giữa "làm đúng việc cần làm" và "chỉ làm những việc được phép làm".
Ví dụ trên là cách mình diễn giải bài học khi thiết kế test, không phải hành vi cụ thể của PoC. Nó cũng không có nghĩa functional test vô dụng. Bạn vẫn cần biết phần mềm mở được file, trả đúng kết quả và cài đặt được. Chỉ nên tránh dùng kết quả đó làm bằng chứng cho một thuộc tính chưa kiểm tra.
Với phần mềm mình phụ trách, mình sẽ muốn ghi rõ phạm vi của từng phép kiểm tra. Một dòng "tests passed" đứng riêng không cho người đọc biết test đã quan sát output, network, filesystem hay chỉ exit code.

Figure 5, trang 6, Malka và cộng sự, CC BY-SA 4.0. Đọc từ trái sang phải: file ban đầu; phần payload và metadata thêm vào; payload chạy rồi trả điều khiển về entry point cũ. Các vùng .text và .data vẫn giữ nguyên trong ví dụ. Hình giúp phân biệt chương trình tiếp tục làm việc bình thường với việc nó không chạy thêm hành vi khác.
Build lại ra cùng hash vẫn cần một giả định
Theo định nghĩa của Reproducible Builds, cùng source, môi trường build và chỉ dẫn build phải cho phép các bên tái tạo artifact giống nhau từng bit. Môi trường bao gồm những yếu tố liên quan như dependency, cấu hình và biến môi trường. Định nghĩa chính thức
Mình đọc định nghĩa này như một cam kết về khả năng tái tạo. Từ đó, có một suy luận cần giữ riêng: nếu hai bên dùng chung một đầu vào đã bị can thiệp, sự đồng ý giữa hai kết quả chưa tự xác minh được đầu vào chung ấy.
Giống như nhờ hai người kiểm tra một phép tính nhưng đưa cho cả hai cùng một bảng số liệu sai. Kết quả trùng nhau vẫn có ích để phát hiện khác biệt trong cách tính; nó chưa trả lời bảng số liệu đến từ đâu.
Vì vậy, câu mình muốn hỏi một independent rebuilder là: bạn độc lập ở những đầu vào nào? Khác máy vật lý, khác người vận hành và khác nguồn gốc toolchain là các mức tách biệt cần mô tả, thay vì gói hết trong chữ "independent".

Minh họa sự khác nhau giữa hai phép kiểm tra cùng gốc và khác gốc. Đây không phải sơ đồ đầy đủ của thuật toán DDC.
Sandbox có nhiệm vụ khác
Nix mô tả sandbox như môi trường cách ly: build thấy dependency trong Nix store, thư mục build tạm và những đường dẫn được cấu hình. Trên Linux, Nix còn dùng các namespace để cách ly process và tài nguyên; fixed-output derivation có ngoại lệ về network. Nix Reference Manual, sandbox
Theo cách mình hiểu ranh giới này, sandbox giúp giới hạn những gì một quá trình build có thể chạm tới. Nó không tự đánh giá nội dung của từng công cụ bạn chủ động đưa vào.
Bạn vẫn phải cho một bước tạo sản phẩm quyền ghi sản phẩm. Nếu chính công cụ được cấp quyền ấy làm sai việc, vấn đề có thể nằm trong phạm vi quyền cho phép, không cần xuất hiện một lần "vượt sandbox" nào.
Điểm này quan trọng khi đọc các đề xuất phòng vệ. Bật sandbox và xác minh nguồn gốc build tool giải quyết những phần khác nhau của cùng pipeline. Mình muốn làm cả hai, thay vì dùng tên một cơ chế để thay cho toàn bộ lập luận về độ tin cậy.
Những điểm mình muốn phản biện
Mình cho rằng nên đánh giá nghiên cứu theo từng đóng góp, thay vì chấp nhận hoặc bác bỏ cả paper. Việc triển khai được một chuỗi lây nhiễm không tự chứng minh cách diễn giải lịch sử của ý tưởng hay đánh giá các biện pháp phòng vệ cũng đầy đủ.
Một triển khai cụ thể của ý tưởng đã có
Mình không xem việc chuyển Trusting Trust ra ngoài compiler là một phát hiện mới về nguyên lý. Thompson đã nêu assembler, loader và microcode trong bài gốc. Đóng góp cần đánh giá ở đây là cách hiện thực hóa và kiểm tra sự lan truyền qua một chuỗi build cụ thể. Thompson, 1984
Phân biệt như vậy vẫn dành chỗ cho công sức kỹ thuật của tác giả. Một ý tưởng đã được mô tả chưa trả lời hết những khó khăn khi triển khai nó. Nhưng mình cũng không muốn dùng kết quả triển khai để gán tính mới cho một nguyên lý có từ trước.
Phạm vi DDC rộng hơn việc thay compiler
Mình không đồng ý với việc đánh giá DDC chỉ trong trường hợp hai nhánh kiểm chứng tiếp tục dùng chung một strip đã nhiễm. Điều kiện đó giữ lại đúng thành phần cần kiểm tra, nên chưa đủ để kết luận về khả năng phòng vệ khi thay cả môi trường.
Mục 6.3, Diversity in environment trong luận án của David A. Wheeler định nghĩa môi trường gồm CPU architecture, hệ điều hành, thư viện hỗ trợ và hạ tầng liên quan. Ông thảo luận dùng môi trường khác để đối phó Trojan nằm trong chính các executable hỗ trợ. Vì vậy, cách giới hạn DDC vào riêng compiler bỏ sót một phần phương pháp đã được mô tả. Luận án Wheeler

Ảnh chụp trực tiếp mục 6.3 của luận án. Vùng vàng cho thấy Wheeler đã đưa hạ tầng hỗ trợ vào phạm vi đa dạng hóa, rộng hơn việc thay compiler. Chụp ngày 08/09/2026, crop và thêm highlight; nội dung gốc không đổi. David A. Wheeler, CC BY-SA 3.0 US.
DDC cũng không đơn giản là build cùng một ứng dụng bằng GCC và Clang rồi yêu cầu hai file giống nhau. Ở trường hợp compiler tự build, cách giải thích rút gọn là dùng compiler tin cậy để build compiler cần kiểm tra, dùng kết quả build nó thêm lần nữa, rồi so với executable cần xác minh. Kết luận phụ thuộc các giả định của quy trình. Wheeler, tổng quan DDC
Mình sẽ không biến điều đó thành lời hứa rằng cứ đổi compiler là phát hiện được backdoor. Một kết quả lệch còn cần điều tra: khác biệt cấu hình hay tính không xác định cũng có thể làm phép so sánh thất bại. Trong thiết kế kiểm chứng, bạn phải ghi những giả định ấy trước khi đọc kết quả.
Phạm vi ảnh hưởng phải gắn với revision
Mình kiểm tra PR #479322: tác giả mô tả nối minimal-bootstrap vào các stdenv glibc và musl của x86_64-linux và i686-linux, bắt đầu từ seed hex0 tối giản. GitHub ghi nhận merge vào staging ngày 28/01/2026.

Ảnh chụp PR #479322 ngày 08/09/2026. Highlight đánh dấu trạng thái đã merge, nhánh đích và ngày GitHub hiển thị; đoạn mô tả bên dưới nêu các platform x86 và seed hex0. API của cùng PR xác nhận timestamp 2026-01-28T16:12:33Z. Mình chỉ crop và thêm highlight.
Đó là mốc cần đặt cạnh ngày công bố nghiên cứu. Tuy nhiên, merge vào staging không đồng nghĩa mọi người dùng, channel và bản pin cũ đã nhận thay đổi. Maintainer cũng nói tới việc chờ chu kỳ staging hoàn tất trong thảo luận NixOS.
Với mình, revision và đường bootstrap là phần bắt buộc của kết luận về phạm vi ảnh hưởng. Bạn phải kiểm tra chúng trên hệ thống đang đánh giá. Một demo chạy trên cấu hình đã chọn không thay thế việc kiểm tra ấy; một PR phòng vệ cũng không thay thế nó theo chiều ngược lại.
Mình sẽ thay đổi gì khi review một pipeline?
Trước khi bổ sung công cụ bảo mật, mình muốn vẽ lại đường đi của artifact. Bắt đầu ở source đã review, đánh dấu từng bước còn quyền sửa binary, rồi ghi nguồn gốc của executable thực hiện bước đó. Đây là đề xuất của mình sau khi đọc các nguồn, không phải danh sách bảo đảm loại bỏ mọi tấn công.
Trong một buổi review, mình sẽ dành thời gian cho bốn câu hỏi:
- Sau compiler còn ai được quyền ghi? Bao gồm hậu xử lý và đóng gói, không chỉ lệnh build chính.
- Công cụ đó đến từ đâu? Một đường dẫn và version chưa nói hết lịch sử tạo ra binary.
- Lần rebuild dùng lại những gì? Ghi rõ cache, seed và toolchain dùng chung trước khi gọi kết quả là kiểm chứng độc lập.
- Bằng chứng nào đủ để mình phản ứng? Phân biệt thay đổi hash, khác biệt đã giải thích được và dấu hiệu hành vi không mong muốn.
Chi phí ở đây nằm ở việc hiểu và duy trì những câu trả lời đó. Một team giao phần mềm hàng tuần khó có thể dành toàn bộ thời gian kiểm toán bootstrap. Mình không nghĩ cách xử lý là yêu cầu mọi developer tự tạo một hệ điều hành. Team cần chọn mức kiểm chứng tương xứng với thứ mình phân phối, đồng thời nói thật về phần còn đang tin vào nhà cung cấp.
Đó cũng là lý do mình dè chừng cả hai cách kể: headline khiến người đọc tưởng có một thảm họa mới, và lời trấn an rằng một nhãn "reproducible" đã khép lại vấn đề. Cả hai giúp cuộc trao đổi ngắn đi, nhưng khiến người chịu trách nhiệm vận hành thiếu thông tin để quyết định.
Lần tới đọc một báo cáo nói "đã build lại và hash trùng", mình vẫn xem đó là một kết quả đáng giữ. Mình chỉ muốn đọc thêm một dòng bên dưới: lần build thứ hai đã độc lập với lần đầu ở những đâu.
Bình
Nguồn và phạm vi
Các link nguồn nằm cạnh nội dung tương ứng. Phần phản biện đối chiếu nghiên cứu với bài gốc của Thompson, luận án Wheeler