Tarball "khó nhất" khiến 8 công cụ giải nén khác nhau

Công nghệ10 tháng 11, 2026·4 phút đọc

Một tarball được tạo bằng duy nhất một lệnh GNU tar nhưng khi giải nén lại cho ra 8 kết quả khác nhau tùy công cụ. Trường hợp này phơi bày những lỗ hổng trong cách các thư viện tar diễn giải đặc tả POSIX.

Tarball "khó nhất" khiến 8 công cụ giải nén khác nhau

Một file nén tar (tarball) được tạo chỉ bằng một lệnh GNU tar duy nhất nhưng khi giải nén lại cho ra tới 8 kết quả khác nhau, tùy vào công cụ hay thư viện bạn dùng. Trường hợp này cho thấy sự mơ hồ trong đặc tả POSIX có thể dẫn tới những hành vi rất khó đoán ở các thư viện xử lý định dạng tar.

Cách tạo ra tarball này

Tarball được tạo từ ba tệp file, ./file và empty, với lệnh:

$ printf hello > file
$ touch empty
$ tar --format=pax --pax-option='delete=?time,size:=512' \
--transform='s,^\./file$,café,' \
-cf hardest.tar file ./file empty

Điểm mấu chốt là tệp file được lặp lại, khiến GNU tar lưu bản thứ hai dưới dạng hard link (liên kết cứng). Lợi dụng đường dẫn duy nhất của mục thứ hai, hard link được đổi tên thành café. Việc này khiến tar tạo một pax header (phần đầu mở rộng của định dạng pax) để lưu tên tệp dưới dạng UTF-8.

Sau đó, một bản ghi size=512 được thêm vào pax header, ghi đè giá trị mặc định size=0 của hard link. Con số 512 không phải ngẫu nhiên: nó đúng bằng kích thước của một tar header.

Về mặt trực quan, archive có hai cách hiểu cơ bản, phụ thuộc vào việc hard link có được phép mang kích thước hay không. POSIX nói rằng các khối dữ liệu của hard link "có thể được bao gồm" trong archive pax, nhưng hệ quả của điều này không hoàn toàn rõ ràng: 512 byte sau hard link có phải là dữ liệu của nó, hay là header của tệp empty như lúc đóng gói? Và làm sao một hard link lại có nội dung khác với tệp gốc cùng liên kết?

Khi giải nén

Kết quả dưới đây được tạo bằng các phiên bản mới nhất của mọi gói tại thời điểm bài viết.

GNU tar, BusyBox, Python, Go và Perl xử lý khá hợp lý: chúng bỏ qua kích thước trên hard link và giải nén ra file, hard link café cùng empty.

bsdtar — tiện ích tar mặc định trên macOS, gần với tinh thần POSIX hơn về khái niệm kích thước trên hard link — ghi 512 byte vào chính hard link. Kết quả là file chứa header tar của empty.

7-Zip, node-tar, tar-fs, cùng thư viện tar của Rust và astral-tokio-tar đồng ý với bsdtar rằng 512 byte là dữ liệu của hard link, nhưng không ghi chúng vào hard link. Thay vào đó chúng nhảy qua phần dữ liệu này, để lại chỉ file và café, không có empty.

Commons Compress của Java cần nhiều sức tưởng tượng hơn: vì café có dữ liệu, một tệp riêng được tạo chứa header của empty làm nội dung, còn file giữ nguyên nội dung ban đầu.

PharData của PHP biến café thành một tệp rỗng riêng biệt và cũng giải nén empty.

RubyGems không giải nén café chút nào, nhưng bù lại tạo một thư mục rỗng PaxHeaders bên cạnh file và empty.

R đi theo hướng hoàn toàn khác: nó giải nén empty với 512 byte null. Công cụ này nhầm lẫn áp kích thước cho empty thay vì cho hard link, và 512 byte đó lấy từ dấu kết thúc archive (end-of-archive marker).

Haskell sẽ giống hệt GNU tar, nếu không mắc lỗi giải mã chuỗi khiến tệp café trở thành café trên đĩa — vào năm 2026, đây không phải lỗi hiển thị của trình duyệt.

.NET, Julia và tar-codec của uv từ chối giải nén tarball. Tác giả cho rằng đây là cách xử lý tốt nhất, dù về mặt kỹ thuật là không tuân thủ POSIX.

Bộ sưu tập tarball hiểm hóc

Tarball này là một trong 134 trường hợp tarball hiểm hóc mà tác giả đang thu thập và phân loại trong bộ kiểm thử haampie/tar-minefield. Trang web https://stoppels.ch/tar có một bảng lớn cho thấy cách từng bản hiện thực xử lý mọi trường hợp, kèm thông tin chi tiết cho từng ca kiểm thử. Một biến thể của tarball trong bài cũng được đưa vào làm ca kiểm thử.

Góc nhìn

Với lập trình viên và kỹ sư làm việc với các định dạng lưu trữ, câu chuyện này là lời nhắc rằng "tuân thủ đặc tả" không đồng nghĩa với "xử lý giống nhau". Cùng một tarball có thể cho ra nội dung khác nhau trên macOS, trong một pipeline CI dùng Python, hay trong một dịch vụ viết bằng Rust — và sự khác biệt đó chỉ lộ ra khi gặp đầu vào dị thường.

Hệ quả thực tế là nếu hệ thống của bạn giải nén dữ liệu từ nguồn không tin cậy, việc chọn thư viện tar không chỉ là chuyện hiệu năng hay tiện dụng. Những bản hiện thực từ chối archive bất thường như .NET, Julia hay tar-codec của uv cho thấy một hướng tiếp cận an toàn hơn: thà báo lỗi còn hơn tạo ra trạng thái trên đĩa mà chính bạn không lường trước được.

Nguồn: stoppels.ch · Quy trình biên tập

Chia sẻ:FacebookX