From 11d545bd21b2eb2f58cf0e6acdae8995e29be4d7 Mon Sep 17 00:00:00 2001 From: Will Lehnertz <6597817+packetThrower@users.noreply.github.com> Date: Tue, 29 Sep 2026 10:02:40 -0700 Subject: [PATCH 1/2] fix(appimage): AppRun runnable by every user, not just root MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cargo-packager builds the AppImage with AppRun at 0744, and the image stores every file as root-owned, so for any other user AppRun is read-only. The AppImage runtime's FUSE mount tolerates that, but a kernel squashfs mount enforces it: firejail, which the AppImage catalog tests with, refuses to start the app ("AppRun: Permission denied") — the same failure Zorite hit on AppImage/appimage.github.io#7154. Re-pack after cargo-packager with AppRun 0755 (appimagetool, no update information embedded), and fail the build if any executable is left owner-only. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/release.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b2236bd..f7b1ef3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -265,6 +265,41 @@ jobs: CARGO_BUILD_TARGET: ${{ matrix.triple }} run: cargo packager --release -f deb -f appimage --target ${{ matrix.triple }} + # cargo-packager ships AppRun as 0744, and the AppImage stores every + # file as root-owned, so for anyone but root AppRun is read-only. The + # AppImage runtime's FUSE mount tolerates that; a kernel squashfs mount + # doesn't — firejail (the AppImage catalog's test harness) refuses to + # start it ("AppRun: Permission denied"). Re-pack with AppRun 0755, and + # fail the build if any executable is left owner-only. appimagetool + # only publishes a `continuous` tag, so there's no version to pin; with + # neither -u nor -g it embeds no update information. + - name: Make AppRun executable for every user + run: | + set -e + APPIMAGE=$(find "target/${{ matrix.triple }}/release" -maxdepth 1 -name '*.AppImage' | head -1) + if [ -z "$APPIMAGE" ]; then + echo "::error::no .AppImage produced by cargo-packager" + exit 1 + fi + ARCH="${{ matrix.rpm_arch }}" + + curl -fsSL -o appimagetool \ + "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${ARCH}.AppImage" + chmod +x appimagetool + TOOL="$PWD/appimagetool" + + cd "$(dirname "$APPIMAGE")" + NAME=$(basename "$APPIMAGE") + rm -rf squashfs-root + "./$NAME" --appimage-extract >/dev/null + chmod 0755 squashfs-root/AppRun + if find squashfs-root -type f -perm -u=x ! -perm -o=x | grep .; then + echo "::error::executables above aren't runnable by other users" + exit 1 + fi + ARCH="$ARCH" "$TOOL" squashfs-root "$NAME" + rm -rf squashfs-root + # cargo-packager 0.11 doesn't expose .deb postinst hooks, so # we patch the freshly-built .deb to inject one that reloads # udev rules + retriggers attached USB devices. Without this, From 8683f8b0a55eab6dbf956fdfb995f9dd47a75aac Mon Sep 17 00:00:00 2001 From: Will Lehnertz <6597817+packetThrower@users.noreply.github.com> Date: Tue, 29 Sep 2026 10:19:06 -0700 Subject: [PATCH 2/2] chore(clippy): as_chunks for the u16 decoders (Rust 1.98 lint) Rust 1.98's new chunks_exact_to_as_chunks lint fails clippy -D warnings on every branch: seven UTF-16 / u16 decoders used chunks_exact(2). as_chunks::<2>() yields [u8; 2] arrays and drops a trailing odd byte just as chunks_exact does, so decoding is unchanged. Co-Authored-By: Claude Opus 5.5 --- src/uefi/fv.rs | 18 ++++++++++++------ src/uefi/mod.rs | 16 ++++++++++------ src/uefi/nvram.rs | 6 ++++-- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/uefi/fv.rs b/src/uefi/fv.rs index d11c606..739ccf9 100644 --- a/src/uefi/fv.rs +++ b/src/uefi/fv.rs @@ -107,8 +107,10 @@ fn scan_fv_offsets(buf: &[u8]) -> Vec { continue; }; let sum: u16 = full_header - .chunks_exact(2) - .map(|c| u16::from_le_bytes([c[0], c[1]])) + .as_chunks::<2>() + .0 + .iter() + .map(|c| u16::from_le_bytes(*c)) .fold(0u16, |a, v| a.wrapping_add(v)); if sum == 0 { out.push(start); @@ -352,8 +354,10 @@ fn find(haystack: &[u8], needle: &[u8]) -> Option { /// Decode a NUL-terminated little-endian UCS-2 string. fn ucs2_to_string(data: &[u8]) -> String { let units: Vec = data - .chunks_exact(2) - .map(|c| u16::from_le_bytes([c[0], c[1]])) + .as_chunks::<2>() + .0 + .iter() + .map(|c| u16::from_le_bytes(*c)) .take_while(|&u| u != 0) .collect(); String::from_utf16_lossy(&units) @@ -451,8 +455,10 @@ mod tests { h[55] = 2; // revision // Fix up the header checksum so the u16 sum is zero. let sum: u16 = h - .chunks_exact(2) - .map(|c| u16::from_le_bytes([c[0], c[1]])) + .as_chunks::<2>() + .0 + .iter() + .map(|c| u16::from_le_bytes(*c)) .fold(0u16, |a, v| a.wrapping_add(v)); h[50..52].copy_from_slice(&(0u16.wrapping_sub(sum)).to_le_bytes()); diff --git a/src/uefi/mod.rs b/src/uefi/mod.rs index 3a9acc8..8a5bd24 100644 --- a/src/uefi/mod.rs +++ b/src/uefi/mod.rs @@ -197,8 +197,8 @@ fn boot_from_nvram(nvram: &HashMap>) -> Vec { return Vec::new(); }; let mut out = Vec::new(); - for chunk in order.chunks_exact(2) { - let num = u16::from_le_bytes([chunk[0], chunk[1]]); + for chunk in order.as_chunks::<2>().0 { + let num = u16::from_le_bytes(*chunk); let slot = format!("Boot{num:04X}"); if let Some(data) = nvram.get(&slot) && let Some(entry) = parse_load_option(&slot, data) @@ -230,8 +230,10 @@ fn parse_load_option(slot: &str, data: &[u8]) -> Option { /// Decode a NUL-terminated little-endian UCS-2 string. fn ucs2_z(b: &[u8]) -> String { let units: Vec = b - .chunks_exact(2) - .map(|c| u16::from_le_bytes([c[0], c[1]])) + .as_chunks::<2>() + .0 + .iter() + .map(|c| u16::from_le_bytes(*c)) .take_while(|&u| u != 0) .collect(); String::from_utf16_lossy(&units) @@ -488,8 +490,10 @@ fn ucs2_at(data: &[u8], offset: usize, max_chars: usize) -> Option { let end = (offset + max_chars * 2).min(data.len()); let slice = data.get(offset..end)?; let units: Vec = slice - .chunks_exact(2) - .map(|c| u16::from_le_bytes([c[0], c[1]])) + .as_chunks::<2>() + .0 + .iter() + .map(|c| u16::from_le_bytes(*c)) .take_while(|&u| u != 0) .collect(); let s = String::from_utf16_lossy(&units); diff --git a/src/uefi/nvram.rs b/src/uefi/nvram.rs index ca87cc2..d83a071 100644 --- a/src/uefi/nvram.rs +++ b/src/uefi/nvram.rs @@ -115,8 +115,10 @@ fn parse_vss_vars(image: &[u8], mut p: usize, end: usize, vars: &mut HashMap String { let units: Vec = b - .chunks_exact(2) - .map(|c| u16::from_le_bytes([c[0], c[1]])) + .as_chunks::<2>() + .0 + .iter() + .map(|c| u16::from_le_bytes(*c)) .take_while(|&u| u != 0) .collect(); String::from_utf16_lossy(&units)