Nix cần các binary có thể tái định vị
(fzakaria.com)- Nix, một trình quản lý gói dựa trên store, được thiết kế để đặt các gói vào một prefix cố định như
/nix/store, nên tạo ra nhiều ràng buộc trong môi trường rootless Nix muốn đặt store ở vị trí khác mà không cần cài Nix sẵn hoặc có quyền root - Nếu dùng
--store /tmp/...cùng vớichrootvà mount namespace, có thể giữ nguyên cùng một hash như bản build/nix/storehiện có, nhờ đó vẫn tiếp tục tận dụng được binary cache nhưcache.nixos.org - Nếu đổi store prefix bằng
local?store=/tmp/...mà không dùng namespace, hash sẽ thay đổi, và ngay cả một bản buildhellođơn giản cũng có thể dẫn tới việc làm mất hiệu lực toàn bộ đồ thị phụ thuộc và biên dịch lại GCC - Cốt lõi của đề xuất là dùng đường dẫn tương đối dựa trên
$ORIGINmà trình liên kết động Linux hỗ trợ, thay vì đường dẫn tuyệt đối trong ELFRUNPATH, để việc thay đổi vị trí store không lan sang thay đổi hash và tái biên dịch - Nút thắt thực sự ngăn khả năng tái định vị là kernel không hỗ trợ
$ORIGINtrong ELFPT_INTERPvà shebang của script; các hướng giải quyết được đề xuất gồm vá kernel, wrapper tĩnh, đường dẫn tương đối theo từng ngôn ngữ, và metadatarelocatable = true;
Xung đột giữa store prefix cố định và rootless Nix
- Các hệ thống dựa trên store như Nix và Guix lưu mọi gói dưới một prefix đã định
- Nix dùng
/nix/store - Guix dùng
/gnu/store
- Nix dùng
- Cấu trúc này giúp việc viết lại đường dẫn binary hay thư viện trở nên dễ dàng
- Ví dụ,
/bin/bashcó thể được thay bằng toàn bộ đường dẫn store như/nix/store/gik3rh1vz2jlgnifb9dh6vc6sxwwz9jj-bash-5.3p9/bin/bash
- Ví dụ,
- Cũng có những tình huống muốn đặt store ở vị trí khác
- Môi trường chưa cài sẵn Nix
- Môi trường không có đủ quyền cần thiết
- Những trường hợp như vậy dẫn tới bài toán “rootless Nix”
- Hiện tại Nix vẫn cho phép chỉ định đường dẫn store khác, nhưng tùy cách làm mà có giữ nguyên hash hay không
nix build nixpkgs#hellocài vào/nix/store/zi2bj2hlavv8q743li2s9diqbcpmrf9b-hello-2.12.3/nix build --store /tmp/fzakaria/store nixpkgs#hellodùngchrootvà mount namespace để cài vào/tmp/fzakaria/store/nix/store/zi2bj2hlavv8q743li2s9diqbcpmrf9b-hello-2.12.3/- Trong cả hai trường hợp, hash
zi2bj2hlavv8q743li2s9diqbcpmrf9bđều giống nhau
- Nếu hash giống nhau, có thể tận dụng các derivation được tính toán sẵn từ binary substituter như https://cache.nixos.org
Cái giá khi đổi store mà không dùng namespace
- Các công cụ như Bazel hay Buck2 có thể đã dùng namespace sẵn cho sandbox riêng của chúng
- Nếu tích hợp Nix vào các hệ sinh thái này, tính thực dụng sẽ giảm mạnh vì user namespace lồng nhau và các giới hạn mount
- Vẫn có thể chỉ định store prefix thay thế mà không dùng
chroothay mount namespace, nhưng có nhược điểm là hash bị đổi- Ví dụ lệnh dùng
--store 'local?store=/tmp/fzakaria/store&state=/tmp/fzakaria/state&log=/tmp/fzakaria/log' - Kết quả đường dẫn
hellolà/tmp/fzakaria/store/qv3fhi1j9gh27fyds5n5b16yia8i6zn5-hello-2.12.3 - Hash không còn là
zi2...mà đổi thànhqv3fhi1j9gh27fyds5n5b16yia8i6zn5
- Ví dụ lệnh dùng
- Chỉ một thay đổi đơn giản ở chuỗi store prefix cũng có thể làm mất hiệu lực dây chuyền của toàn bộ đồ thị phụ thuộc
- Chỉ để in ra “Hello World” trong một thư mục khác mà có thể phải biên dịch GCC suốt 4 tiếng
- Trong trường hợp này cũng không thể tận dụng cache công khai
- Giới hạn này hiện cũng đã được nêu rõ trong tài liệu Nix
$ORIGIN giải quyết được gì và giới hạn còn lại ở kernel
- Gốc rễ vấn đề là store prefix là một phần của chính derivation, nên ảnh hưởng tới việc tính hash
- Nếu không dùng toàn bộ store prefix ở mọi nơi mà chuyển sang đường dẫn tương đối, có thể tránh được thay đổi hash
- ELF
RUNPATHlà một trong những điểm có thể áp dụng- Ví dụ
RUNPATHhiện tại củahellolà/nix/store/57iz36553175g3178pvxjij8z5rcsd4n-glibc-2.42-61/lib - Loader của Linux hỗ trợ
$ORIGIN, tức thư mục chứa file thực thi - Vì vậy có thể viết
RUNPATHthành$ORIGIN/../../57iz36553175g3178pvxjij8z5rcsd4n-glibc-2.42-61/lib - Làm vậy thì dù vị trí store thay đổi, hash vẫn không đổi và cũng không cần tái biên dịch
- Ví dụ
- Tuy nhiên trước khi dynamic linker đọc
RUNPATH, kernel Linux phải tự tải chính dynamic linker đó trước- Đường dẫn này được lưu trong header
PT_INTERPcủa ELF - Ví dụ là
/nix/store/57iz36553175g3178pvxjij8z5rcsd4n-glibc-2.42-61/lib/ld-linux-x86-64.so.2 - Hiện tại kernel Linux không hỗ trợ
$ORIGINtrongPT_INTERP
- Đường dẫn này được lưu trong header
- Shebang của script cũng gặp cùng một giới hạn
- Ví dụ
#!/nix/store/gik3rh1vz2jlgnifb9dh6vc6sxwwz9jj-bash-5.3p9/bin/bash - Kernel kỳ vọng một đường dẫn tuyệt đối khi phân tích
#! - Hiện tại shebang cũng không hỗ trợ
$ORIGIN
- Ví dụ
- Có thể dùng đường dẫn tương đối theo thư mục làm việc hiện tại, nhưng cách này dễ hỏng khi chạy script từ nơi khác nên không đáng tin cậy
Đề xuất hướng tới binary có thể tái định vị
- Để tạo ra binary thật sự có thể tái định vị, cần lách qua hoặc thay đổi các giới hạn của kernel
- Có ba hướng tiếp cận được đề xuất
- Vá kernel Linux để hỗ trợ
$ORIGINtrongPT_INTERPvà shebang - Bọc mọi binary bằng một binary tĩnh nhỏ, để wrapper tự tính vị trí của nó rồi chạy dynamic linker
- Thay đổi cả vị trí file để tận dụng khả năng đường dẫn tương đối theo từng ngôn ngữ
- Trong Python, có thể dùng
__file__để truy cập file dựa trên chính vị trí của nó
- Trong Python, có thể dùng
- Vá kernel Linux để hỗ trợ
- Hướng được đề xuất là phù hợp nhất là mở rộng hỗ trợ trong kernel Linux
- Trên máy NixOS, có thể vá kernel bằng Nix để thêm hỗ trợ này
- Ngoài ra còn có đề xuất thêm metadata
relocatable = true;cho từng derivation để biểu thị liệu nó có thể tái định vị hay không
1 bình luận
Ý kiến trên Lobste.rs
Sẽ rất tốt nếu nhân Linux hỗ trợ
$ORIGINtrongPT_INTERP. Trước đây tôi từng thử bằng một binary wrapper tĩnh, và cũng đã thấy vài nỗ lực khác (ví dụ hay); tất cả đều là những pha hack rất xuất sắc và đẹp mắt, nhưng rốt cuộc vẫn chỉ là hackTôi vẫn chưa thực sự hiểu rõ các hàm ý về bảo mật, nên nếu có một phần giải thích được hệ thống hóa thì sẽ rất hữu ích
Có vẻ Solaris hỗ trợ điều này, nên có thể tồn tại cách làm an toàn. Khó tìm được bằng chứng rõ ràng, nhưng trong mục ENOEXEC của tài liệu
execve(2)có nói rằng nếu program headerPT_INTERPcủa file image tiến trình setuid/setgid dùng đường dẫn tương đối hoặc token$ORIGINthì sẽ thất bạiCó phải rất nhiều phần mềm có đường dẫn được ghi cứng tại thời điểm build hoặc các hằng số, nên cuối cùng vẫn phải biên dịch lại thì mới chạy đúng không?
outPaththông qua{foo}, nên nếu một trong các dependency cấp trên được đổi sang local store thì sẽ phải build lạiBản vá dcrt1 cho musl (do rcombs viết) giải quyết vấn đề này ở không gian người dùng
Liệu có thể tạo một file system được mount tại
/originđể nó được diễn giải thành$ORIGINkhông? Như vậy có vẻ sẽ hoạt động cho cả shebang lẫn ELF mà không cần thêm cú pháp/origin, thì chẳng phải cũng có thể tạo luôn/nixvà chạynix-daemonsao?Nếu binary có thể chỉ định một loader đi kèm bằng đường dẫn tương đối, có thể là không an toàn, thì đó chẳng phải là một rủi ro bảo mật sao?
libc.so.6?