1
0
mirror of https://github.com/containers/bootc.git synced 2026-02-05 06:45:13 +01:00

build-sys: Use tmpfs for /run to prevent mount stub leakage

When using --mount=type=bind,target=/run/foo, podman/buildah creates
the mount point directory in the image layer even though the mounted
content is not committed. These empty directory stubs pollute /run
in the final image.

Fix by using --mount=type=tmpfs,target=/run with bind mounts nested
inside. This ensures /run remains empty in the committed layer.

Also move the lint invocation in Dockerfile.cfsuki to a separate RUN
command so it runs after the bind mount is released.

Assisted-by: OpenCode (Opus 4.5)
Signed-off-by: Colin Walters <walters@verbum.org>
This commit is contained in:
Colin Walters
2026-01-14 13:54:38 -05:00
parent 315bfb3cfd
commit 69ec7d6c04
4 changed files with 118 additions and 60 deletions

View File

@@ -13,10 +13,11 @@ const DOCKERFILE_NETWORK_CUTOFF: &str = "external dependency cutoff point";
///
/// - Reproducible builds for the RPM
/// - Dockerfile network isolation after cutoff point
/// - Dockerfile tmpfs on /run for all RUN instructions
#[context("Checking build system")]
pub fn check_buildsys(sh: &Shell, dockerfile_path: &Utf8Path) -> Result<()> {
check_package_reproducibility(sh)?;
check_dockerfile_network_isolation(dockerfile_path)?;
check_dockerfile_rules(dockerfile_path)?;
Ok(())
}
@@ -61,24 +62,28 @@ fn check_package_reproducibility(sh: &Shell) -> Result<()> {
Ok(())
}
/// Verify that all RUN instructions in the Dockerfile after the network cutoff
/// point include `--network=none`.
#[context("Checking Dockerfile network isolation")]
fn check_dockerfile_network_isolation(dockerfile_path: &Utf8Path) -> Result<()> {
println!("Checking Dockerfile network isolation...");
/// Verify Dockerfile rules:
/// - All RUN instructions must include `--mount=type=tmpfs,target=/run`
/// - After cutoff, all RUN instructions must start with `--network=none`
#[context("Checking Dockerfile rules")]
fn check_dockerfile_rules(dockerfile_path: &Utf8Path) -> Result<()> {
println!("Checking Dockerfile rules...");
let dockerfile = std::fs::read_to_string(dockerfile_path).context("Reading Dockerfile")?;
verify_dockerfile_network_isolation(&dockerfile)?;
println!("ok Dockerfile network isolation");
verify_dockerfile_rules(&dockerfile)?;
println!("ok Dockerfile rules");
Ok(())
}
const RUN_NETWORK_NONE: &str = "RUN --network=none";
const RUN_TMPFS: &str = "--mount=type=tmpfs,target=/run";
/// Verify that all RUN instructions after the network cutoff marker start with
/// `RUN --network=none`.
/// Verify Dockerfile rules:
/// - All RUN instructions must include `--mount=type=tmpfs,target=/run` to prevent
/// podman's DNS resolver files from leaking into the image
/// - After the network cutoff, all RUN instructions must start with `--network=none`
///
/// Returns Ok(()) if all RUN instructions comply, or an error listing violations.
pub fn verify_dockerfile_network_isolation(dockerfile: &str) -> Result<()> {
pub fn verify_dockerfile_rules(dockerfile: &str) -> Result<()> {
// Find the cutoff point
let cutoff_line = dockerfile
.lines()
@@ -90,19 +95,26 @@ pub fn verify_dockerfile_network_isolation(dockerfile: &str) -> Result<()> {
)
})?;
// Check all RUN instructions after the cutoff point
let mut errors = Vec::new();
for (idx, line) in dockerfile.lines().enumerate().skip(cutoff_line + 1) {
for (idx, line) in dockerfile.lines().enumerate() {
let line_num = idx + 1; // 1-based line numbers
let trimmed = line.trim();
// Check if this is a RUN instruction
if trimmed.starts_with("RUN ") {
// Must start with exactly "RUN --network=none"
if !trimmed.starts_with(RUN_NETWORK_NONE) {
// All RUN instructions must include tmpfs mount on /run
if !trimmed.contains(RUN_TMPFS) {
errors.push(format!(
" line {}: RUN instruction must start with `{}`",
" line {}: RUN instruction must include `{}`",
line_num, RUN_TMPFS
));
}
// After cutoff, must start with exactly "RUN --network=none"
if idx > cutoff_line && !trimmed.starts_with(RUN_NETWORK_NONE) {
errors.push(format!(
" line {}: RUN instruction after cutoff must start with `{}`",
line_num, RUN_NETWORK_NONE
));
}
@@ -111,9 +123,7 @@ pub fn verify_dockerfile_network_isolation(dockerfile: &str) -> Result<()> {
if !errors.is_empty() {
anyhow::bail!(
"Dockerfile has RUN instructions after '{}' that don't start with `{}`:\n{}",
DOCKERFILE_NETWORK_CUTOFF,
RUN_NETWORK_NONE,
"Dockerfile has invalid RUN instructions:\n{}",
errors.join("\n")
);
}
@@ -126,40 +136,73 @@ mod tests {
use super::*;
#[test]
fn test_network_isolation_valid() {
fn test_dockerfile_rules_valid() {
let dockerfile = r#"
FROM base
RUN echo "before cutoff, no network restriction needed"
RUN --mount=type=tmpfs,target=/run echo "before cutoff, network allowed"
# external dependency cutoff point
RUN --network=none echo "good"
RUN --network=none --mount=type=bind,from=foo,target=/bar some-command
RUN --network=none --mount=type=tmpfs,target=/run echo "good"
RUN --network=none --mount=type=tmpfs,target=/run --mount=type=bind,from=foo,target=/bar some-command
"#;
verify_dockerfile_network_isolation(dockerfile).unwrap();
verify_dockerfile_rules(dockerfile).unwrap();
}
#[test]
fn test_network_isolation_missing_flag() {
fn test_dockerfile_rules_missing_tmpfs_before_cutoff() {
let dockerfile = r#"
FROM base
RUN echo "bad - missing tmpfs"
# external dependency cutoff point
RUN --network=none echo "good"
RUN echo "bad - missing network flag"
RUN --network=none --mount=type=tmpfs,target=/run echo "good"
"#;
let err = verify_dockerfile_network_isolation(dockerfile).unwrap_err();
let err = verify_dockerfile_rules(dockerfile).unwrap_err();
let msg = err.to_string();
assert!(msg.contains("line 3"), "error should mention line 3: {msg}");
assert!(msg.contains("tmpfs"), "error should mention tmpfs: {msg}");
}
#[test]
fn test_dockerfile_rules_missing_network_flag_after_cutoff() {
let dockerfile = r#"
FROM base
RUN --mount=type=tmpfs,target=/run echo "before cutoff"
# external dependency cutoff point
RUN --mount=type=tmpfs,target=/run echo "bad - missing network flag"
"#;
let err = verify_dockerfile_rules(dockerfile).unwrap_err();
let msg = err.to_string();
assert!(msg.contains("line 5"), "error should mention line 5: {msg}");
assert!(
msg.contains("--network=none"),
"error should mention --network=none: {msg}"
);
}
#[test]
fn test_network_isolation_wrong_position() {
fn test_dockerfile_rules_missing_tmpfs_after_cutoff() {
let dockerfile = r#"
FROM base
RUN --mount=type=tmpfs,target=/run echo "before cutoff"
# external dependency cutoff point
RUN --network=none echo "bad - missing tmpfs"
"#;
let err = verify_dockerfile_rules(dockerfile).unwrap_err();
let msg = err.to_string();
assert!(msg.contains("line 5"), "error should mention line 5: {msg}");
assert!(msg.contains("tmpfs"), "error should mention tmpfs: {msg}");
}
#[test]
fn test_dockerfile_rules_network_wrong_position() {
// --network=none must come immediately after RUN
let dockerfile = r#"
FROM base
RUN --mount=type=tmpfs,target=/run echo "before cutoff"
# external dependency cutoff point
RUN --mount=type=bind,from=foo,target=/bar --network=none echo "bad"
RUN --mount=type=tmpfs,target=/run --network=none echo "bad - network flag not first"
"#;
let err = verify_dockerfile_network_isolation(dockerfile).unwrap_err();
let err = verify_dockerfile_rules(dockerfile).unwrap_err();
let msg = err.to_string();
assert!(msg.contains("line 4"), "error should mention line 4: {msg}");
assert!(msg.contains("line 5"), "error should mention line 5: {msg}");
}
}