1 पॉइंट द्वारा GN⁺ 3 시간 전 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • store-आधारित package manager Nix को इस तरह डिज़ाइन किया गया है कि packages को /nix/store जैसे fixed prefix में रखा जाए, इसलिए ऐसे rootless Nix environment में, जहाँ मौजूदा Nix install या root permission के बिना store को किसी दूसरी location पर रखना हो, काफ़ी सीमाएँ सामने आती हैं
  • --store /tmp/... और chroot·mount namespace को साथ में इस्तेमाल करने पर मौजूदा /nix/store builds जैसा ही hash बनाए रखा जा सकता है, जिससे cache.nixos.org जैसे binary cache का उपयोग जारी रह सकता है
  • namespace के बिना local?store=/tmp/... से store prefix बदलने पर hash बदल जाता है, और साधारण hello build भी पूरे 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
  • इस संरचना में 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#hello chroot और 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' का उपयोग करती है
    • नतीजे में hello path /tmp/fzakaria/store/qv3fhi1j9gh27fyds5n5b16yia8i6zn5-hello-2.12.3 बनता है
    • hash पहले वाले zi2... की जगह qv3fhi1j9gh27fyds5n5b16yia8i6zn5 हो जाता है
  • 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_INTERP header में store होता है
    • उदाहरण: /nix/store/57iz36553175g3178pvxjij8z5rcsd4n-glibc-2.42-61/lib/ld-linux-x86-64.so.2
    • अभी Linux kernel PT_INTERP में $ORIGIN को support नहीं करता
  • script shebang पर भी यही सीमा लागू होती है
    • उदाहरण: #!/nix/store/gik3rh1vz2jlgnifb9dh6vc6sxwwz9jj-bash-5.3p9/bin/bash
    • kernel, #! parse करते समय absolute path की अपेक्षा करता है
    • shebang में भी अभी $ORIGIN support नहीं है
  • current working directory के आधार पर relative path इस्तेमाल किए जा सकते हैं, लेकिन script को किसी दूसरी location से चलाने पर वे टूट सकते हैं, इसलिए उन पर भरोसा करना मुश्किल है

relocatable binaries की ओर बढ़ने का प्रस्ताव

  • सच में relocatable binaries बनाने के लिए kernel की इन सीमाओं को bypass करना या बदलना होगा
  • प्रस्तावित approaches तीन हैं
    • Linux kernel को patch करके PT_INTERP और shebang में $ORIGIN support जोड़ा जाए
    • सभी binaries को एक छोटे static binary wrapper में लपेटा जाए, और wrapper अपनी location निकालकर dynamic linker को execute करे
    • file location handling को language-specific relative path features का उपयोग करने लायक बदला जाए
      • Python में __file__ के ज़रिए अपने ही आधार पर files को access किया जा सकता है
  • सबसे उपयुक्त approach के रूप में Linux kernel support का विस्तार प्रस्तावित है
    • NixOS machines पर Nix के ज़रिए kernel patch करके यह support जोड़ा जा सकता है
  • इसके अलावा हर derivation में relocatable होने की स्थिति बताने के लिए relocatable = true; metadata जोड़ने का भी प्रस्ताव है

1 टिप्पणियां

 
GN⁺ 3 시간 전
Lobste.rs की राय
  • अच्छा होगा अगर Linux kernel में PT_INTERP के लिए $ORIGIN support आ जाए। पहले इसे static wrapper binary से आज़माया गया था, और मैंने कई दूसरी कोशिशें भी देखी हैं(अच्छा उदाहरण), लेकिन वे सब बेहतरीन और शानदार hacks हैं, फिर भी आखिरकार hacks ही हैं
    इसके security implications को मैं अभी पूरी तरह नहीं समझ पाया हूँ, इसलिए अगर इसका कोई व्यवस्थित explanation हो तो मदद मिलेगी
    लगता है Solaris इसे support करता है, इसलिए शायद इसे सुरक्षित तरीके से करने का कोई तरीका हो सकता है। ठोस संदर्भ ढूंढना मुश्किल है, लेकिन execve(2) manual के ENOEXEC में लिखा है कि अगर setuid/setgid process image file के PT_INTERP program header में relative path हो या $ORIGIN token इस्तेमाल किया गया हो, तो वह fail हो जाता है

  • क्या बहुत-सा software build time पर hardcode किए गए paths या constants नहीं रखता, इसलिए उसे ठीक से चलाने के लिए आखिरकार फिर से compile करना ही पड़ेगा?

    • ऐसे मामले पहले से बहुत हैं, और ज़्यादातर shebang lines में ऐसा होता है। Nix में build time पर ऐसे values replace करने के लिए कई helpers शामिल हैं
    • सही है, लेकिन यह समस्या local store से अलग भी पहले से मौजूद थी। शायद lower derivations {foo} के जरिए outPath consume करते होंगे, और अगर 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 भी चलाया जा सकता है, है न?
    • मेरा अनुमान है कि NixOS के बाहर भी compatibility बनाए रखने के लिए लक्ष्य यह होगा कि बिना किसी अतिरिक्त file system या daemon की installation/configuration के काम चल जाए
  • अगर binary relative path में, शायद असुरक्षित और self-provided loader specify कर सके, तो क्या यह security risk नहीं होगा?

    • इस threat model में किस पर भरोसा है और किस पर नहीं? अगर आप वैसे भी उस binary को चलाने वाले हैं, तो क्या बात सिर्फ इतनी है कि आप उसे verified loader से चलाना चाहते हैं?
    • इसे libc.so.6 जैसी दूसरी dynamic link libraries के search path तय करने वाले environment variables से कम सुरक्षित क्यों माना जाए?