Nix को relocatable binaries की ज़रूरत है
(fzakaria.com)- store-आधारित package manager Nix को इस तरह डिज़ाइन किया गया है कि packages को
/nix/storeजैसे fixed prefix में रखा जाए, इसलिए ऐसे rootless Nix environment में, जहाँ मौजूदा Nix install या root permission के बिना store को किसी दूसरी location पर रखना हो, काफ़ी सीमाएँ सामने आती हैं --store /tmp/...औरchroot·mount namespace को साथ में इस्तेमाल करने पर मौजूदा/nix/storebuilds जैसा ही hash बनाए रखा जा सकता है, जिससेcache.nixos.orgजैसे binary cache का उपयोग जारी रह सकता है- namespace के बिना
local?store=/tmp/...से store prefix बदलने पर hash बदल जाता है, और साधारणhellobuild भी पूरे dependency graph के invalidation और GCC के दोबारा compilation तक पहुँच सकता है - प्रस्ताव का मुख्य विचार यह है कि ELF
RUNPATHमें absolute path की जगह Linux dynamic linker द्वारा समर्थित$ORIGIN-आधारित relative path का उपयोग किया जाए, ताकि store location बदलने का असर hash और recompilation तक न फैले - वास्तव में relocation को रोकने वाली बड़ी रुकावट यह है कि kernel, ELF
PT_INTERPऔर script shebang में$ORIGINको support नहीं करता; इसके समाधान के तौर पर kernel patch, static wrapper, language-specific relative path, औरrelocatable = true;metadata का प्रस्ताव दिया गया है
fixed store prefix और rootless Nix के बीच टकराव
- Nix और Guix जैसे store-आधारित systems सभी packages को एक तय prefix के नीचे store करते हैं
- Nix:
/nix/store - Guix:
/gnu/store
- Nix:
- इस संरचना में binary या library path को rewrite करना आसान होता है
- उदाहरण के लिए
/bin/bashको/nix/store/gik3rh1vz2jlgnifb9dh6vc6sxwwz9jj-bash-5.3p9/bin/bashजैसे पूरे store path से बदला जा सकता है
- उदाहरण के लिए
- कुछ स्थितियों में store को किसी और location पर रखना ज़रूरी हो सकता है
- जहाँ Nix पहले से install न हो
- जहाँ ज़रूरी permissions उपलब्ध न हों
- ऐसे ही मामले “rootless Nix” समस्या की ओर ले जाते हैं
- Nix अभी भी अलग store path specify कर सकता है, लेकिन तरीका यह तय करता है कि hash बना रहेगा या नहीं
nix build nixpkgs#helloइसे/nix/store/zi2bj2hlavv8q743li2s9diqbcpmrf9b-hello-2.12.3/में install करता हैnix build --store /tmp/fzakaria/store nixpkgs#hellochrootऔर mount namespace का उपयोग करके इसे/tmp/fzakaria/store/nix/store/zi2bj2hlavv8q743li2s9diqbcpmrf9b-hello-2.12.3/में install करता है- दोनों मामलों में hash
zi2bj2hlavv8q743li2s9diqbcpmrf9bएक जैसा रहता है
- अगर hash एक जैसा रहे, तो https://cache.nixos.org जैसे binary substituter से precomputed derivation का उपयोग किया जा सकता है
namespace के बिना store बदलने की लागत
- Bazel या Buck2 जैसे tools अपनी sandboxing के लिए पहले से namespace का उपयोग कर सकते हैं
- ऐसे ecosystem में Nix को integrate करने की कोशिश nested user namespace और mount restrictions की वजह से कम व्यावहारिक हो जाती है
chrootऔर mount namespace के बिना भी alternative store prefix दिया जा सकता है, लेकिन इसमें hash बदलने की समस्या है- उदाहरण command
--store 'local?store=/tmp/fzakaria/store&state=/tmp/fzakaria/state&log=/tmp/fzakaria/log'का उपयोग करती है - नतीजे में
hellopath/tmp/fzakaria/store/qv3fhi1j9gh27fyds5n5b16yia8i6zn5-hello-2.12.3बनता है - hash पहले वाले
zi2...की जगहqv3fhi1j9gh27fyds5n5b16yia8i6zn5हो जाता है
- उदाहरण command
- store prefix string में यह मामूली बदलाव पूरे dependency graph को श्रृंखलाबद्ध तरीके से invalidate कर देता है
- सिर्फ़ किसी दूसरे folder से “Hello World” प्रिंट करने के लिए GCC को 4 घंटे compile करना पड़ सकता है
- इस स्थिति में public cache का फ़ायदा नहीं मिल पाता
- यह सीमा मौजूदा Nix documentation में भी साफ़ तौर पर दर्ज है
$ORIGIN क्या हल करता है, और kernel की क्या सीमाएँ बची रहती हैं
- समस्या की जड़ यह है कि store prefix derivation का ही हिस्सा होता है, इसलिए उसका असर hash calculation पर पड़ता है
- अगर हर जगह पूरे store prefix की जगह relative path का उपयोग हो, तो hash change से बचा जा सकता है
- ELF binary का
RUNPATHऐसा एक बिंदु है जहाँ यह लागू हो सकता है- अभी
helloकेRUNPATHका उदाहरण/nix/store/57iz36553175g3178pvxjij8z5rcsd4n-glibc-2.42-61/libहै - Linux loader executable वाली directory के लिए
$ORIGINको support करता है - इसलिए
RUNPATHको$ORIGIN/../../57iz36553175g3178pvxjij8z5rcsd4n-glibc-2.42-61/libकी तरह लिखा जा सकता है - ऐसा करने पर store location बदलने से hash नहीं बदलेगा और recompilation की ज़रूरत भी नहीं पड़ेगी
- अभी
- लेकिन dynamic linker के
RUNPATHपढ़ने से पहले Linux kernel को dynamic linker खुद load करना पड़ता है- यह path ELF के
PT_INTERPheader में store होता है - उदाहरण:
/nix/store/57iz36553175g3178pvxjij8z5rcsd4n-glibc-2.42-61/lib/ld-linux-x86-64.so.2 - अभी Linux kernel
PT_INTERPमें$ORIGINको support नहीं करता
- यह path ELF के
- script shebang पर भी यही सीमा लागू होती है
- उदाहरण:
#!/nix/store/gik3rh1vz2jlgnifb9dh6vc6sxwwz9jj-bash-5.3p9/bin/bash - kernel,
#!parse करते समय absolute path की अपेक्षा करता है - shebang में भी अभी
$ORIGINsupport नहीं है
- उदाहरण:
- current working directory के आधार पर relative path इस्तेमाल किए जा सकते हैं, लेकिन script को किसी दूसरी location से चलाने पर वे टूट सकते हैं, इसलिए उन पर भरोसा करना मुश्किल है
relocatable binaries की ओर बढ़ने का प्रस्ताव
- सच में relocatable binaries बनाने के लिए kernel की इन सीमाओं को bypass करना या बदलना होगा
- प्रस्तावित approaches तीन हैं
- Linux kernel को patch करके
PT_INTERPऔर shebang में$ORIGINsupport जोड़ा जाए - सभी binaries को एक छोटे static binary wrapper में लपेटा जाए, और wrapper अपनी location निकालकर dynamic linker को execute करे
- file location handling को language-specific relative path features का उपयोग करने लायक बदला जाए
- Python में
__file__के ज़रिए अपने ही आधार पर files को access किया जा सकता है
- Python में
- Linux kernel को patch करके
- सबसे उपयुक्त approach के रूप में Linux kernel support का विस्तार प्रस्तावित है
- NixOS machines पर Nix के ज़रिए kernel patch करके यह support जोड़ा जा सकता है
- इसके अलावा हर derivation में relocatable होने की स्थिति बताने के लिए
relocatable = true;metadata जोड़ने का भी प्रस्ताव है
1 टिप्पणियां
Lobste.rs की राय
अच्छा होगा अगर Linux kernel में
PT_INTERPके लिए$ORIGINsupport आ जाए। पहले इसे static wrapper binary से आज़माया गया था, और मैंने कई दूसरी कोशिशें भी देखी हैं(अच्छा उदाहरण), लेकिन वे सब बेहतरीन और शानदार hacks हैं, फिर भी आखिरकार hacks ही हैंइसके security implications को मैं अभी पूरी तरह नहीं समझ पाया हूँ, इसलिए अगर इसका कोई व्यवस्थित explanation हो तो मदद मिलेगी
लगता है Solaris इसे support करता है, इसलिए शायद इसे सुरक्षित तरीके से करने का कोई तरीका हो सकता है। ठोस संदर्भ ढूंढना मुश्किल है, लेकिन
execve(2)manual के ENOEXEC में लिखा है कि अगर setuid/setgid process image file केPT_INTERPprogram header में relative path हो या$ORIGINtoken इस्तेमाल किया गया हो, तो वह fail हो जाता हैक्या बहुत-सा software build time पर hardcode किए गए paths या constants नहीं रखता, इसलिए उसे ठीक से चलाने के लिए आखिरकार फिर से compile करना ही पड़ेगा?
{foo}के जरिएoutPathconsume करते होंगे, और अगर upper dependencies में से किसी एक को local store में बदल दिया जाए, तो फिर rebuild करना पड़ेगाmusl के लिए dcrt1 patch (rcombs द्वारा) इस समस्या को user space में हल करता है
क्या
/originपर mounted file system बनाकर उसे$ORIGINकी तरह resolve नहीं किया जा सकता? तब शायद यह बिना अतिरिक्त syntax के shebang और ELF दोनों में काम करेगा/originबनाया और mount किया जा सकता है, तो फिर बस/nixबनाकरnix-daemonभी चलाया जा सकता है, है न?अगर binary relative path में, शायद असुरक्षित और self-provided loader specify कर सके, तो क्या यह security risk नहीं होगा?
libc.so.6जैसी दूसरी dynamic link libraries के search path तय करने वाले environment variables से कम सुरक्षित क्यों माना जाए?