lol

Merge pull request #314268 from DianQK/rustup-patchelf

rustup: addressing a series of issues encountered while using Rust's self-contained `ld.lld`

authored by

Jörg Thalheim and committed by
GitHub
3d59539c 9a4cd7ce

+188 -22
+167 -21
pkgs/development/tools/rust/rustup/0001-dynamically-patchelf-binaries.patch
··· 1 1 diff --git a/src/dist/component/package.rs b/src/dist/component/package.rs 2 - index 73a533b5..408ab815 100644 2 + index dfccc661..85233f3b 100644 3 3 --- a/src/dist/component/package.rs 4 4 +++ b/src/dist/component/package.rs 5 - @@ -113,6 +113,7 @@ fn install<'a>( 5 + @@ -113,6 +113,7 @@ impl Package for DirectoryPackage { 6 6 } else { 7 7 builder.move_file(path.clone(), &src_path)? 8 8 } 9 - + nix_patchelf_if_needed(&target.prefix().path().join(path.clone()), &src_path) 9 + + nix_patchelf_if_needed(&target.prefix().path().join(path.clone())) 10 10 } 11 11 "dir" => { 12 12 if self.copy { 13 - @@ -135,6 +136,29 @@ fn components(&self) -> Vec<String> { 13 + @@ -135,6 +136,175 @@ impl Package for DirectoryPackage { 14 14 } 15 15 } 16 16 17 - +fn nix_patchelf_if_needed(dest_path: &Path, src_path: &Path) { 18 - + let (is_bin, is_lib) = if let Some(p) = src_path.parent() { 19 - + (p.ends_with("bin") || p.ends_with("libexec"), p.ends_with("lib")) 20 - + } else { 21 - + (false, false) 22 - + }; 17 + +fn nix_wrap_lld(dest_lld_path: &Path) -> Result<()> { 18 + + use std::fs; 19 + + use std::io::Write; 20 + + use std::os::unix::fs::PermissionsExt; 21 + + 22 + + let path = dest_lld_path.parent().unwrap(); 23 + + let mut unwrapped_name = path.file_name().unwrap().to_string_lossy().to_string(); 24 + + unwrapped_name.push_str("-unwrapped"); 25 + + let unwrapped_dir = path.with_file_name(unwrapped_name); 26 + + fs::create_dir(&unwrapped_dir).context("failed to create unwrapped directory")?; 27 + + let mut unwrapped_lld = unwrapped_dir; 28 + + unwrapped_lld.push(dest_lld_path.file_name().unwrap()); 29 + + fs::rename(dest_lld_path, &unwrapped_lld).context("failed to move file")?; 30 + + let mut ld_wrapper_path = std::env::current_exe()? 31 + + .parent() 32 + + .ok_or(anyhow!("failed to get parent directory"))? 33 + + .with_file_name("nix-support"); 34 + + let mut file = std::fs::File::create(dest_lld_path)?; 35 + + ld_wrapper_path.push("ld-wrapper.sh"); 23 36 + 24 - + if is_bin { 25 - + let _ = ::std::process::Command::new("@patchelf@/bin/patchelf") 37 + + let wrapped_script = format!( 38 + + "#!/usr/bin/env bash 39 + +set -eu -o pipefail +o posix 40 + +shopt -s nullglob 41 + +export PROG=\"{}\" 42 + +\"{}\" $@", 43 + + unwrapped_lld.to_string_lossy().to_string(), 44 + + ld_wrapper_path.to_string_lossy().to_string(), 45 + + ); 46 + + file.write_all(wrapped_script.as_bytes())?; 47 + + let mut permissions = file.metadata()?.permissions(); 48 + + permissions.set_mode(0o755); 49 + + file.set_permissions(permissions)?; 50 + + Ok(()) 51 + +} 52 + + 53 + +fn nix_patchelf_if_needed(dest_path: &Path) { 54 + + use std::fs::File; 55 + + use std::os::unix::fs::FileExt; 56 + + 57 + + struct ELFReader<'a> { 58 + + file: &'a mut File, 59 + + is_32bit: bool, 60 + + is_little_end: bool, 61 + + } 62 + + 63 + + impl<'a> ELFReader<'a> { 64 + + const MAGIC_NUMBER: &'static [u8] = &[0x7F, 0x45, 0x4c, 0x46]; 65 + + const ET_EXEC: u16 = 0x2; 66 + + const ET_DYN: u16 = 0x3; 67 + + const PT_INTERP: u32 = 0x3; 68 + + 69 + + fn new(file: &'a mut File) -> Option<Self> { 70 + + let mut magic_number = [0; 4]; 71 + + file.read_exact(&mut magic_number).ok()?; 72 + + if Self::MAGIC_NUMBER != magic_number { 73 + + return None; 74 + + } 75 + + let mut ei_class = [0; 1]; 76 + + file.read_exact_at(&mut ei_class, 0x4).ok()?; 77 + + let is_32bit = ei_class[0] == 1; 78 + + let mut ei_data = [0; 1]; 79 + + file.read_exact_at(&mut ei_data, 0x5).ok()?; 80 + + let is_little_end = ei_data[0] == 1; 81 + + Some(Self { 82 + + file, 83 + + is_32bit, 84 + + is_little_end, 85 + + }) 86 + + } 87 + + 88 + + fn is_exec_or_dyn(&self) -> bool { 89 + + let e_type = self.read_u16_at(0x10); 90 + + e_type == Self::ET_EXEC || e_type == Self::ET_DYN 91 + + } 92 + + 93 + + fn e_phoff(&self) -> u64 { 94 + + if self.is_32bit { 95 + + self.read_u32_at(0x1C) as u64 96 + + } else { 97 + + self.read_u64_at(0x20) 98 + + } 99 + + } 100 + + 101 + + fn e_phentsize(&self) -> u64 { 102 + + let offset = if self.is_32bit { 0x2A } else { 0x36 }; 103 + + self.read_u16_at(offset) as u64 104 + + } 105 + + 106 + + fn e_phnum(&self) -> u64 { 107 + + let offset = if self.is_32bit { 0x2C } else { 0x38 }; 108 + + self.read_u16_at(offset) as u64 109 + + } 110 + + 111 + + fn has_interp(&self) -> bool { 112 + + let e_phoff = self.e_phoff(); 113 + + let e_phentsize = self.e_phentsize(); 114 + + let e_phnum = self.e_phnum(); 115 + + for i in 0..e_phnum { 116 + + let p_type = self.read_u32_at(e_phoff + i * e_phentsize); 117 + + if p_type == Self::PT_INTERP { 118 + + return true; 119 + + } 120 + + } 121 + + false 122 + + } 123 + + 124 + + fn read_u16_at(&self, offset: u64) -> u16 { 125 + + let mut data = [0; 2]; 126 + + self.file.read_exact_at(&mut data, offset).unwrap(); 127 + + if self.is_little_end { 128 + + u16::from_le_bytes(data) 129 + + } else { 130 + + u16::from_be_bytes(data) 131 + + } 132 + + } 133 + + 134 + + fn read_u32_at(&self, offset: u64) -> u32 { 135 + + let mut data = [0; 4]; 136 + + self.file.read_exact_at(&mut data, offset).unwrap(); 137 + + if self.is_little_end { 138 + + u32::from_le_bytes(data) 139 + + } else { 140 + + u32::from_be_bytes(data) 141 + + } 142 + + } 143 + + 144 + + fn read_u64_at(&self, offset: u64) -> u64 { 145 + + let mut data = [0; 8]; 146 + + self.file.read_exact_at(&mut data, offset).unwrap(); 147 + + if self.is_little_end { 148 + + u64::from_le_bytes(data) 149 + + } else { 150 + + u64::from_be_bytes(data) 151 + + } 152 + + } 153 + + } 154 + + 155 + + let Some(mut dest_file) = File::open(dest_path).ok() else { 156 + + return; 157 + + }; 158 + + let Some(elf) = ELFReader::new(&mut dest_file) else { 159 + + return; 160 + + }; 161 + + if !elf.is_exec_or_dyn() { 162 + + return; 163 + + } 164 + + let mut patch_command = std::process::Command::new("@patchelf@/bin/patchelf"); 165 + + if elf.has_interp() { 166 + + patch_command 26 167 + .arg("--set-interpreter") 27 - + .arg("@dynamicLinker@") 28 - + .arg(dest_path) 29 - + .output(); 168 + + .arg("@dynamicLinker@"); 169 + + } 170 + + if Some(std::ffi::OsStr::new("rust-lld")) == dest_path.file_name() || !elf.has_interp() { 171 + + patch_command.arg("--add-rpath").arg("@libPath@"); 172 + + } 173 + + 174 + + debug!("patching {dest_path:?} using patchelf"); 175 + + if let Err(err) = patch_command.arg(dest_path).output() { 176 + + warn!("failed to execute patchelf: {err:?}"); 30 177 + } 31 - + else if is_lib { 32 - + let _ = ::std::process::Command::new("@patchelf@/bin/patchelf") 33 - + .arg("--set-rpath") 34 - + .arg("@libPath@") 35 - + .arg(dest_path) 36 - + .output(); 178 + + 179 + + if Some(std::ffi::OsStr::new("ld.lld")) == dest_path.file_name() { 180 + + if let Err(err) = nix_wrap_lld(dest_path) { 181 + + warn!("failed to wrap `ld.lld`: {err:?}"); 182 + + } 37 183 + } 38 184 +} 39 185 +
+21 -1
pkgs/development/tools/rust/rustup/default.nix
··· 48 48 checkFeatures = [ ]; 49 49 50 50 patches = lib.optionals stdenv.isLinux [ 51 - (runCommand "0001-dynamically-patchelf-binaries.patch" { CC = stdenv.cc; patchelf = patchelf; libPath = "$ORIGIN/../lib:${libPath}"; } '' 51 + (runCommand "0001-dynamically-patchelf-binaries.patch" 52 + { 53 + CC = stdenv.cc; 54 + patchelf = patchelf; 55 + libPath = "${libPath}"; 56 + } '' 52 57 export dynamicLinker=$(cat $CC/nix-support/dynamic-linker) 53 58 substitute ${./0001-dynamically-patchelf-binaries.patch} $out \ 54 59 --subst-var patchelf \ ··· 96 101 # Note: fish completion script is not supported. 97 102 $out/bin/rustup completions bash cargo > "$out/share/bash-completion/completions/cargo" 98 103 $out/bin/rustup completions zsh cargo > "$out/share/zsh/site-functions/_cargo" 104 + 105 + # add a wrapper script for ld.lld 106 + mkdir -p $out/nix-support 107 + substituteAll ${../../../../../pkgs/build-support/wrapper-common/utils.bash} $out/nix-support/utils.bash 108 + substituteAll ${../../../../../pkgs/build-support/bintools-wrapper/add-flags.sh} $out/nix-support/add-flags.sh 109 + substituteAll ${../../../../../pkgs/build-support/bintools-wrapper/add-hardening.sh} $out/nix-support/add-hardening.sh 110 + export prog='$PROG' 111 + export use_response_file_by_default=0 112 + substituteAll ${../../../../../pkgs/build-support/bintools-wrapper/ld-wrapper.sh} $out/nix-support/ld-wrapper.sh 113 + chmod +x $out/nix-support/ld-wrapper.sh 99 114 ''; 115 + 116 + env = lib.optionalAttrs (pname == "rustup") { 117 + inherit (stdenv.cc.bintools) expandResponseParams shell suffixSalt wrapperName coreutils_bin; 118 + hardening_unsupported_flags = ""; 119 + }; 100 120 101 121 meta = with lib; { 102 122 description = "The Rust toolchain installer";