Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Cargo
# will have compiled files and executables
debug
target

# These are backup files generated by rustfmt
**/*.rs.bk

# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

# Generated by cargo mutants
# Contains mutation testing data
**/mutants.out*/

# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
61 changes: 59 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 8 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,28 @@ version = "0.1.10"
edition = "2021"

[dependencies]
clap = { version = "4.4", features = ["derive"] }
reqwest = { version = "0.12", default-features = false, features = ["stream", "rustls-tls", "http2"] }
clap = { version = "4.4", features = ["derive", "env"] }
reqwest = { version = "0.12", default-features = false, features = ["stream", "rustls-tls", "http2", "json"] }
tokio = { version = "1.0", features = ["full", "process", "rt-multi-thread"] }
futures-util = "0.3"
bytes = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
base64 = "0.22"
tar = "0.4"
flate2 = "1.0"
Comment on lines +12 to +16
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's examine the Cargo.toml file to verify the dependencies
if [ -f "Cargo.toml" ]; then
  echo "=== Full Cargo.toml ===" 
  cat -n Cargo.toml
else
  echo "Cargo.toml not found"
  find . -name "Cargo.toml" -type f | head -5
fi

Repository: jumpstarter-dev/fls

Length of output: 1693


🌐 Web query:

security vulnerabilities serde 1.0 rust crate CVE

💡 Result:

Here are the publicly disclosed security issues (CVEs/advisories) related to Serde and Serde-related crates that have affected the 1.0-era ecosystem:

  • CVE-2019-25001 — serde_cbor: CBOR deserializer allowed deeply nested semantic tags causing stack consumption / possible DoS; fixed in serde_cbor 0.10.2. [1][2]
  • CVE-2021-45687 (RUSTSEC-2021-0089) — raw-cpuid: when the non-default serialize feature is enabled, generated serde::Deserialize impls lacked validation (memory-corruption / panics); fixed in raw-cpuid >= 9.1.1. [3][4]
  • Cargo name confusion (CVE-2019-16760) — not a serde code bug but a Cargo behavior issue around the package renaming feature that could cause downloading the wrong crate (attacker-squatting names like serde1); affects builds using older Cargo/Rust. Upgrade Cargo/Rust to eliminate the issue. [5]
  • CVE-2024-58264 — serde-json-wasm: stack-consumption via deeply nested JSON in versions before 1.0.1 (upgrade to >= 1.0.1). [6][7]

Recommended action: audit your dependency tree for crates that depend on serde (and serde-* formats), and upgrade affected crates to the fixed versions above (or to the latest releases). For crates you maintain, avoid enabling non-default features that introduce unchecked Deserialize implementations until you’ve reviewed input validation. Use cargo audit / cargo-deny and monitor RustSec/NVD advisories for ongoing updates.

Sources:

  1. NVD — CVE-2019-25001 (serde_cbor).
  2. OSV / RustSec — RUSTSEC-2019-0025 (serde_cbor).
  3. NVD — CVE-2021-45687.
  4. RustSec advisory RUSTSEC-2021-0089 (raw-cpuid).
  5. RustSec / CVE-2019-16760 (Cargo package rename issue).
  6. Wiz / vulnerability data for CVE-2024-58264 (serde-json-wasm).
  7. RustSec / GitHub advisory entries for serde-json-wasm.

🌐 Web query:

security vulnerabilities base64 0.22 rust crate

💡 Result:

Short answer: there are no public RustSec/CVE advisories specific to base64 crate v0.22.x. The notable security issue for the crate is an older integer-overflow / heap-buffer-overflow advisory (RUSTSEC-2017-0004 / CVE-2017-1000430) that affected much earlier versions and was fixed long ago; v0.22.x is not listed as affected. See sources below.

Sources:

  • RUSTSEC/CVE advisory for the historical base64 bug (RUSTSEC-2017-0004 / CVE-2017-1000430). [1]
  • crate metadata / release docs for base64 0.22.x (no listed advisory for 0.22). [2]
  • Related but separate: a 2024 advisory about non-constant-time base64 use in the vodozemac crate (not the base64 crate itself). [3]

References:
[1] rustsec / OSV RUSTSEC-2017-0004 (CVE-2017-1000430).
[2] base64 0.22.0 crate info (deps.rs / docs.rs).
[3] RUSTSEC-2024-0354 (vodozemac: non-constant-time base64 usage).


🌐 Web query:

security vulnerabilities tar 0.4 flate2 1.0 rust crate CVE

💡 Result:

Short answer:

  • The tar crate has a known CVE: CVE-2021-38511 — directory traversal via symlink extraction affecting tar versions before 0.4.36 (update to >= 0.4.36). [1][2]
  • I found no published CVE for the flate2 1.0 series. Check RustSec / cargo-audit for any advisory affecting your exact flate2 version and run cargo-audit on your Cargo.lock. [2][3]

Sources:

  1. CVE-2021-38511 details (tar before 0.4.36). [CVE/NVD summary]. [1]
  2. RustSec / advisory database & guidance (use cargo-audit). [2]
  3. flate2 crate / repo (no CVE listed in RustSec search results). [3]

🌐 Web query:

security vulnerabilities xz2 0.1 rust crate

💡 Result:

Short answer: yes — xz2 (0.1.x) is a wrapper around liblzma, so security issues in liblzma/xz-utils affect it. Two high‑impact incidents to be aware of:

  • March–April 2024 supply‑chain/backdoor incident (malicious code in xz/liblzma distribution). Affected downstream packages and bindings; advisories recommend using fixed/clean upstream sources. [1][2]
  • CVE‑2025‑31115 (disclosed Apr 3, 2025): a use‑after‑free / NULL‑write bug in the multithreaded liblzma .xz decoder (lzma_stream_decoder_mt) affecting XZ Utils 5.3.3alpha–5.8.0; fixed in 5.8.1. Any crate that links the affected liblzma (including xz2) is potentially impacted if it uses the multithreaded decoder. [3][4]

What to do:

  • If you use xz2, ensure the underlying liblzma is a patched/clean version (liblzma / xz-utils ≥ fixed releases) and/or upgrade xz2 to a release built against a fixed liblzma. Verify your distro/package vendor’s liblzma version or rebuild linking a safe liblzma. [3][4][1]

Sources:
[1] Snyk advisory on embedded malicious code in liblzma / xz (disclosed Mar 29, 2024).
[2] Tukaani (xz) backdoor / incident pages and timeline.
[3] Tukaani advisory for CVE‑2025‑31115 (Apr 3, 2025).
[4] NVD entry for CVE‑2025‑31115.


Fix tar dependency version constraint to address CVE-2021-38511.

The tar 0.4 dependency allows versions below 0.4.36, which are vulnerable to directory traversal via symlink extraction (CVE-2021-38511)—directly relevant to the stated layer extraction functionality. Update to tar = "0.4.36" or use tar = { version = "0.4", features = [...], ">= 0.4.36" }.

Additionally, xz2 0.1 wraps liblzma which has CVE-2025-31115 (use-after-free in multithreaded decoder affecting liblzma versions before 5.8.1). Verify the vendored/linked liblzma version is patched, or upgrade xz2 if a fixed release is available.

The other dependencies (serde, serde_json, base64, flate2) have no known public vulnerabilities in the specified versions.

🤖 Prompt for AI Agents
In @Cargo.toml around lines 12 - 16, The tar dependency in Cargo.toml permits
vulnerable 0.4.x versions; update the tar version constraint to require at least
0.4.36 (e.g., change tar = "0.4" to tar = "0.4.36" or use a semver constraint
like ">= 0.4.36") to mitigate CVE-2021-38511, and audit/update any xz2
dependency (and its linked/liblzma vendoring) to a release that uses liblzma >=
5.8.1 or otherwise confirm the linked liblzma is patched to mitigate
CVE-2025-31115. Ensure Cargo.toml entries for tar and xz2 are updated
accordingly so cargo.lock will pick the fixed versions.

indicatif = "0.17"
libc = "0.2"
nix = { version = "0.29", features = ["ioctl"] }
# Enable vendored OpenSSL for cross-compilation to musl targets
# This ensures OpenSSL builds from source with musl compatibility
openssl = { version = "0.10", features = ["vendored"] }
xz2 = "0.1"

[dev-dependencies]
wiremock = "0.6"
tempfile = "3"
tokio = { version = "1.0", features = ["full", "rt-multi-thread"] }
xz2 = "0.1"
flate2 = "1.0"
hyper = { version = "1.0", features = ["server", "http1"] }
hyper-util = { version = "0.1", features = ["tokio", "server"] }
Expand Down
45 changes: 26 additions & 19 deletions src/fls/block_writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,7 @@ impl BlockWriter {
}
let f = if is_block_dev {
// For block devices, open with read+write (required by some drivers)
OpenOptions::new()
.read(true)
.write(true)
.open(device)?
OpenOptions::new().read(true).write(true).open(device)?
} else {
OpenOptions::new()
.write(true)
Expand All @@ -201,29 +198,35 @@ impl BlockWriter {

#[cfg(target_os = "macos")]
let (file, use_direct_io) = {
use std::os::unix::io::AsRawFd;
use std::os::unix::fs::FileTypeExt;
use std::os::unix::io::AsRawFd;

// Check if this is a block device or character device (raw disk)
let metadata = std::fs::metadata(device)?;
let file_type = metadata.file_type();
let is_block_dev = file_type.is_block_device();
let is_char_dev = file_type.is_char_device();
let (is_block_dev, is_char_dev) = if let Ok(metadata) = std::fs::metadata(device) {
let file_type = metadata.file_type();
(file_type.is_block_device(), file_type.is_char_device())
} else {
// File doesn't exist yet - not a device
(false, false)
};
let is_device = is_block_dev || is_char_dev;

if debug {
eprintln!("[DEBUG] Device type check for {}:", device);
eprintln!("[DEBUG] Block device: {}", is_block_dev);
eprintln!("[DEBUG] Character device (raw): {}", is_char_dev);
eprintln!("[DEBUG] Regular file: {}", file_type.is_file());
eprintln!("[DEBUG] Regular file: {}", !is_device);
}

// On macOS, strongly warn if using /dev/diskN instead of /dev/rdiskN
// Buffered block devices often fail with ioctl errors when writing raw data
if is_block_dev && device.starts_with("/dev/disk") && !device.starts_with("/dev/rdisk") {
if is_block_dev && device.starts_with("/dev/disk") && !device.starts_with("/dev/rdisk")
{
eprintln!("\n⚠️ WARNING: You are using buffered device {}", device);
eprintln!(" On macOS, buffered block devices (/dev/diskN) often fail with 'Inappropriate ioctl' errors");
eprintln!(" when writing raw disk images, especially if the disk has partitions.");
eprintln!(
" when writing raw disk images, especially if the disk has partitions."
);
eprintln!(" \n STRONGLY RECOMMENDED: Use the raw device instead:");
eprintln!(" {}", device.replace("/dev/disk", "/dev/rdisk"));
eprintln!();
Expand Down Expand Up @@ -279,10 +282,7 @@ impl BlockWriter {
let f = if is_device {
// For devices (block or character), don't use create/truncate
// On macOS, raw devices often require read+write access even for write-only operations
OpenOptions::new()
.read(true)
.write(true)
.open(device)?
OpenOptions::new().read(true).write(true).open(device)?
} else {
// For regular files, use create/truncate
OpenOptions::new()
Expand Down Expand Up @@ -473,11 +473,18 @@ impl AsyncBlockWriter {
// On macOS, provide helpful hints for common errors
if e.raw_os_error() == Some(25) {
eprintln!("\n⚠️ Error 25 (Inappropriate ioctl) on macOS:");
if device.starts_with("/dev/disk") && !device.starts_with("/dev/rdisk") {
eprintln!(" You MUST use the raw device: {}", device.replace("/dev/disk", "/dev/rdisk"));
if device.starts_with("/dev/disk") && !device.starts_with("/dev/rdisk")
{
eprintln!(
" You MUST use the raw device: {}",
device.replace("/dev/disk", "/dev/rdisk")
);
eprintln!(" Buffered devices (/dev/diskN) cannot be used for raw disk writes.");
} else {
eprintln!(" Try unmounting the disk: diskutil unmountDisk {}", device);
eprintln!(
" Try unmounting the disk: diskutil unmountDisk {}",
device
);
eprintln!(" Or ejecting it: diskutil eject {}", device);
}
}
Expand Down
28 changes: 28 additions & 0 deletions src/fls/compression.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//! Unified compression type detection and handling
//!
//! This module provides a single source of truth for compression types
//! used across both OCI and URL-based flash operations.

/// Compression type for data streams
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Compression {
/// No compression - raw data
None,
/// Gzip compression (.gz)
Gzip,
/// XZ/LZMA compression (.xz)
Xz,
/// Zstandard compression (.zst)
Zstd,
}

impl std::fmt::Display for Compression {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Compression::None => write!(f, "none"),
Compression::Gzip => write!(f, "gzip"),
Compression::Xz => write!(f, "xz"),
Compression::Zstd => write!(f, "zstd"),
}
}
}
Loading