Nix ต้องการไบนารีที่ย้ายตำแหน่งได้
(fzakaria.com)- Nix ซึ่งเป็น ตัวจัดการแพ็กเกจแบบ store-based ถูกออกแบบให้วางแพ็กเกจไว้ใน prefix แบบตายตัวอย่าง
/nix/storeทำให้มีข้อจำกัดมากในสภาพแวดล้อม rootless Nix ที่ต้องการวาง store ไว้คนละตำแหน่งโดยไม่มีการติดตั้ง Nix เดิมหรือสิทธิ์ root - หากใช้
--store /tmp/...ร่วมกับchrootและ mount namespace ก็จะยังคงใช้แฮชเดียวกับการบิลด์บน/nix/storeเดิมได้ จึงยังใช้ binary cache อย่างcache.nixos.orgต่อไปได้ - ถ้าเปลี่ยน store prefix เป็น
local?store=/tmp/...โดยไม่ใช้ namespace แฮชจะเปลี่ยน และแม้แต่การบิลด์helloง่าย ๆ ก็อาจทำให้กราฟ dependency ทั้งหมดใช้ไม่ได้ต่อเนื่องไปจนถึงการคอมไพล์ GCC ใหม่ - แกนของข้อเสนอคือให้ใช้พาธสัมพัทธ์ที่อิง
$ORIGINซึ่ง Linux dynamic linker รองรับ แทนพาธสัมบูรณ์ใน ELFRUNPATHเพื่อไม่ให้การย้ายตำแหน่ง store ลุกลามไปเป็นการเปลี่ยนแฮชและการคอมไพล์ใหม่ - คอขวดที่ขวางความสามารถในการย้ายตำแหน่งจริง ๆ คือเคอร์เนลยังไม่รองรับ
$ORIGINใน ELFPT_INTERPและ shebang ของสคริปต์ โดยมีการเสนอแนวทางแก้ทั้งการแพตช์เคอร์เนล, static wrapper, พาธสัมพัทธ์เฉพาะภาษา และเมตาดาต้าrelocatable = true;
การชนกันระหว่าง store prefix แบบตายตัวกับ rootless Nix
- ระบบแบบ store-based อย่าง Nix และ Guix จะเก็บแพ็กเกจทั้งหมดไว้ใต้ prefix ที่กำหนดไว้แน่นอน
- Nix ใช้
/nix/store - Guix ใช้
/gnu/store
- Nix ใช้
- โครงสร้างนี้ทำให้เขียนทับพาธของไบนารีหรือไลบรารีได้ง่าย
- ตัวอย่างเช่นสามารถแทน
/bin/bashด้วยพาธ store เต็มอย่าง/nix/store/gik3rh1vz2jlgnifb9dh6vc6sxwwz9jj-bash-5.3p9/bin/bashได้
- ตัวอย่างเช่นสามารถแทน
- แต่ก็มีสถานการณ์ที่ต้องการวาง store ไว้อีกตำแหน่งหนึ่ง
- สภาพแวดล้อมที่ยังไม่ได้ติดตั้ง Nix
- สภาพแวดล้อมที่ไม่มีสิทธิ์ที่ต้องใช้
- กรณีเหล่านี้นำไปสู่ปัญหาที่เรียกว่า “rootless Nix”
- ปัจจุบัน Nix ระบุพาธ store อื่นได้อยู่แล้ว แต่การคงแฮชเดิมหรือไม่นั้นขึ้นกับวิธีที่ใช้
nix build nixpkgs#helloจะติดตั้งลงที่/nix/store/zi2bj2hlavv8q743li2s9diqbcpmrf9b-hello-2.12.3/nix build --store /tmp/fzakaria/store nixpkgs#helloจะใช้chrootและ mount namespace เพื่อติดตั้งลงที่/tmp/fzakaria/store/nix/store/zi2bj2hlavv8q743li2s9diqbcpmrf9b-hello-2.12.3/- ทั้งสองกรณีใช้แฮช
zi2bj2hlavv8q743li2s9diqbcpmrf9bเหมือนกัน
- เมื่อแฮชเหมือนกัน ก็สามารถใช้ derivation ที่คำนวณไว้ล่วงหน้าจาก binary substituter อย่าง https://cache.nixos.org ได้
ต้นทุนของการเปลี่ยน store โดยไม่ใช้ namespace
- เครื่องมืออย่าง Bazel หรือ Buck2 อาจใช้ namespace อยู่แล้วเพื่อทำ sandboxing ของตัวเอง
- ถ้าจะรวม Nix เข้าไปใน ecosystem แบบนี้ ความสามารถใช้งานจริงจะลดลงมากเพราะข้อจำกัดของ nested user namespace และ mount
- แม้จะกำหนด store prefix ทางเลือกได้โดยไม่ใช้
chrootและ mount namespace แต่มีข้อเสียคือแฮชจะเปลี่ยน- ตัวอย่างคำสั่งใช้
--store 'local?store=/tmp/fzakaria/store&state=/tmp/fzakaria/state&log=/tmp/fzakaria/log' - ผลลัพธ์คือพาธ
helloจะเป็น/tmp/fzakaria/store/qv3fhi1j9gh27fyds5n5b16yia8i6zn5-hello-2.12.3 - แฮชไม่ใช่
zi2...เดิม แต่เปลี่ยนเป็นqv3fhi1j9gh27fyds5n5b16yia8i6zn5
- ตัวอย่างคำสั่งใช้
- การเปลี่ยนเพียงสตริงของ store prefix สามารถทำให้ กราฟ dependency ทั้งหมด ใช้ไม่ได้เป็นลูกโซ่
- แค่ต้องการให้ “Hello World” ทำงานจากอีกโฟลเดอร์หนึ่ง ก็อาจจบลงที่ต้องคอมไพล์ GCC นาน 4 ชั่วโมง
- ในกรณีนี้จะใช้ public cache ไม่ได้
- ข้อจำกัดนี้ถูกระบุไว้ชัดเจนใน เอกสารของ Nix แล้ว
สิ่งที่ $ORIGIN ช่วยได้ และข้อจำกัดของเคอร์เนลที่ยังเหลืออยู่
- ต้นตอของปัญหาคือ store prefix เป็นส่วนหนึ่งของ derivation เอง จึงมีผลต่อการคำนวณแฮช
- ถ้าไม่ใช้ store prefix เต็มทุกที่ แต่เปลี่ยนไปใช้พาธสัมพัทธ์ ก็จะเลี่ยงการเปลี่ยนแฮชได้
- จุดหนึ่งที่ทำได้คือ
RUNPATHของไบนารี ELF- ตัวอย่าง
RUNPATHของhelloปัจจุบันคือ/nix/store/57iz36553175g3178pvxjij8z5rcsd4n-glibc-2.42-61/lib - Linux loader รองรับ
$ORIGINซึ่งหมายถึงไดเรกทอรีที่ไฟล์ executable อยู่ - ดังนั้นจึงเขียน
RUNPATHเป็น$ORIGIN/../../57iz36553175g3178pvxjij8z5rcsd4n-glibc-2.42-61/libได้ - แบบนี้แม้ตำแหน่งของ store จะเปลี่ยน ก็ไม่ทำให้แฮชเปลี่ยนและไม่ต้องคอมไพล์ใหม่
- ตัวอย่าง
- แต่ก่อนที่ dynamic linker จะอ่าน
RUNPATHได้ Linux kernel ต้องเป็นฝ่ายโหลด dynamic linker เองก่อน- พาธนี้ถูกเก็บไว้ใน header
PT_INTERPของ ELF - ตัวอย่างคือ
/nix/store/57iz36553175g3178pvxjij8z5rcsd4n-glibc-2.42-61/lib/ld-linux-x86-64.so.2 - ปัจจุบัน Linux kernel ยังไม่รองรับ
$ORIGINในPT_INTERP
- พาธนี้ถูกเก็บไว้ใน header
- shebang ของสคริปต์ก็มีข้อจำกัดแบบเดียวกัน
- ตัวอย่างคือ
#!/nix/store/gik3rh1vz2jlgnifb9dh6vc6sxwwz9jj-bash-5.3p9/bin/bash - เคอร์เนลคาดหวังพาธแบบสัมบูรณ์เมื่อ parse
#! - ปัจจุบัน shebang ก็ยังไม่รองรับ
$ORIGIN
- ตัวอย่างคือ
- แม้จะใช้พาธสัมพัทธ์อิงจากไดเรกทอรีทำงานปัจจุบันได้ แต่ถ้ารันสคริปต์จากที่อื่นก็จะพัง จึงเชื่อถือได้ยาก
ข้อเสนอเพื่อมุ่งไปสู่ไบนารีที่ย้ายตำแหน่งได้
- หากจะสร้าง ไบนารีที่ย้ายตำแหน่งได้จริง ก็ต้องหลบหรือเปลี่ยนข้อจำกัดของเคอร์เนลเหล่านี้
- แนวทางที่เสนอมี 3 แบบ
- แพตช์ Linux kernel ให้รองรับ
$ORIGINในPT_INTERPและ shebang - ครอบไบนารีทั้งหมดด้วยไบนารี static ขนาดเล็ก โดยให้ wrapper คำนวณตำแหน่งของตัวเองก่อนแล้วจึงเรียก dynamic linker
- ปรับตำแหน่งไฟล์ให้ใช้ความสามารถพาธสัมพัทธ์เฉพาะภาษา
- ใน Python สามารถใช้
__file__เพื่อเข้าถึงไฟล์โดยอิงจากตำแหน่งของตัวเองได้
- ใน Python สามารถใช้
- แพตช์ Linux kernel ให้รองรับ
- แนวทางที่ถูกเสนอว่าเหมาะสมที่สุดคือ การขยายการรองรับใน Linux kernel
- บนเครื่อง NixOS สามารถใช้ Nix แพตช์เคอร์เนลเพื่อเพิ่มความสามารถนี้ได้
- นอกจากนี้ยังมีข้อเสนอให้ใส่เมตาดาต้า
relocatable = true;ในแต่ละ derivation เพื่อระบุว่าย้ายตำแหน่งได้หรือไม่
1 ความคิดเห็น
ความเห็นจาก Lobste.rs
น่าจะดีถ้าในเคอร์เนล Linux มี การรองรับ
$ORIGINในPT_INTERPเคยลองทำด้วยไบนารี wrapper แบบ static มาก่อน และก็เคยเห็นความพยายามอื่นอีกหลายแบบ(ตัวอย่างที่ดี) ซึ่งทั้งหมดก็เป็นแฮ็กที่ยอดเยี่ยมและชาญฉลาด แต่สุดท้ายก็ยังเป็นแฮ็กอยู่ดีผม/ฉันยังไม่เข้าใจนัยด้านความปลอดภัยของเรื่องนี้ดีนัก เลยคิดว่าถ้ามีคำอธิบายที่เรียบเรียงไว้ก็น่าจะช่วยได้
ดูเหมือน Solaris จะรองรับสิ่งนี้ อาจมีวิธีทำให้ปลอดภัยก็ได้ หาแหล่งอ้างอิงชัด ๆ ยาก แต่ใน ENOEXEC ของคู่มือ
execve(2)ระบุว่า ถ้า program headerPT_INTERPของไฟล์อิมเมจโปรเซส setuid/setgid มีพาธแบบสัมพัทธ์หรือใช้โทเค็น$ORIGINก็จะล้มเหลวซอฟต์แวร์จำนวนมากมี พาธหรือค่าคงที่ที่ฝังไว้ตอน build ดังนั้นสุดท้ายก็ต้องคอมไพล์ใหม่เพื่อให้มันทำงานได้ถูกต้องไม่ใช่หรือ?
outPathผ่าน{foo}แต่ถ้าเปลี่ยนหนึ่งใน dependency ชั้นบนให้เป็น local store ก็ต้อง build ใหม่แพตช์ dcrt1 สำหรับ musl (เขียนโดย rcombs) แก้ปัญหานี้ใน user space
จะสร้างไฟล์ซิสเต็มที่เมานต์ไว้ที่
/originแล้วให้มันถูกตีความเป็น$ORIGINได้ไหม? ถ้าได้ แบบนั้นน่าจะใช้ได้ทั้งกับ shebang และ ELF โดยไม่ต้องเพิ่มไวยากรณ์อะไร/originได้ ก็อาจจะสร้าง/nixแล้วรันnix-daemonไปเลยไม่ใช่หรือ?ถ้าไบนารีสามารถระบุ loader ที่จัดมาเองผ่านพาธสัมพัทธ์ได้ ซึ่งอาจไม่ปลอดภัย แบบนั้นจะเป็นความเสี่ยงด้านความปลอดภัยหรือเปล่า?
libc.so.6?