Skip to content

Commit 137a6cb

Browse files
committed
build-sys: Add tmpfs mount for /tmp, allow lint to see /tmp and /run
In C9S there's something leaking files in `/tmp` so let's just enforce use of tmpfs for `/run` at build time too. But fix `RUN bootc container lint` to *not* have those mounts becuase otherwise we don't actually see the leaked content. Assisted-by: Cursor (Opus 4.5) Signed-off-by: Colin Walters <walters@verbum.org>
1 parent c2de54e commit 137a6cb

3 files changed

Lines changed: 100 additions & 46 deletions

File tree

Dockerfile

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ FROM $base as buildroot
2323
ARG initramfs=1
2424
# This installs our buildroot, and we want to cache it independently of the rest.
2525
# Basically we don't want changing a .rs file to blow out the cache of packages.
26-
RUN --mount=type=tmpfs,target=/run \
26+
RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \
2727
--mount=type=bind,from=packaging,src=/,target=/run/packaging \
2828
/run/packaging/install-buildroot
2929
# Now copy the rest of the source
@@ -32,11 +32,11 @@ WORKDIR /src
3232
# See https://www.reddit.com/r/rust/comments/126xeyx/exploring_the_problem_of_faster_cargo_docker/
3333
# We aren't using the full recommendations there, just the simple bits.
3434
# First we download all of our Rust dependencies
35-
RUN --mount=type=tmpfs,target=/run --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome cargo fetch
35+
RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome cargo fetch
3636

3737
FROM buildroot as sdboot-content
3838
# Writes to /out
39-
RUN --mount=type=tmpfs,target=/run /src/contrib/packaging/configure-systemdboot download
39+
RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp /src/contrib/packaging/configure-systemdboot download
4040

4141
# We always do a "from scratch" build
4242
# https://docs.fedoraproject.org/en-US/bootc/building-from-scratch/
@@ -49,17 +49,17 @@ RUN --mount=type=tmpfs,target=/run /src/contrib/packaging/configure-systemdboot
4949
FROM $base as target-base
5050
# Handle version skew between base image and mirrors for CentOS Stream
5151
# xref https://gitlab.com/redhat/centos-stream/containers/bootc/-/issues/1174
52-
RUN --mount=type=tmpfs,target=/run \
52+
RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \
5353
--mount=type=bind,from=packaging,src=/,target=/run/packaging \
5454
/run/packaging/enable-compose-repos
55-
RUN --mount=type=tmpfs,target=/run /usr/libexec/bootc-base-imagectl build-rootfs --manifest=standard /target-rootfs
55+
RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp /usr/libexec/bootc-base-imagectl build-rootfs --manifest=standard /target-rootfs
5656

5757
FROM scratch as base
5858
COPY --from=target-base /target-rootfs/ /
5959
# SKIP_CONFIGS=1 skips LBIs, test kargs, and install configs (for FCOS testing)
6060
ARG SKIP_CONFIGS
61-
# Use tmpfs,target=/run with bind mounts inside to avoid leaking mount stubs into the image
62-
RUN --mount=type=tmpfs,target=/run \
61+
# Use tmpfs for /run and /tmp with bind mounts inside to avoid leaking mount stubs into the image
62+
RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \
6363
--mount=type=bind,from=src,src=/src/hack,target=/run/hack \
6464
cd /run/hack/ && SKIP_CONFIGS="${SKIP_CONFIGS}" ./provision-derived.sh
6565
# Note we don't do any customization here yet
@@ -88,12 +88,12 @@ ARG pkgversion
8888
ARG SOURCE_DATE_EPOCH
8989
ENV SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH}
9090
# Build RPM directly from source, using cached target directory
91-
RUN --network=none --mount=type=tmpfs,target=/run --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome RPM_VERSION="${pkgversion}" /src/contrib/packaging/build-rpm
91+
RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome RPM_VERSION="${pkgversion}" /src/contrib/packaging/build-rpm
9292

9393
FROM buildroot as sdboot-signed
9494
# The secureboot key and cert are passed via Justfile
9595
# We write the signed binary into /out
96-
RUN --network=none --mount=type=tmpfs,target=/run \
96+
RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \
9797
--mount=type=bind,from=sdboot-content,src=/,target=/run/sdboot-package \
9898
--mount=type=secret,id=secureboot_key \
9999
--mount=type=secret,id=secureboot_cert \
@@ -104,22 +104,22 @@ FROM build as units
104104
# A place that we're more likely to be able to set xattrs
105105
VOLUME /var/tmp
106106
ENV TMPDIR=/var/tmp
107-
RUN --network=none --mount=type=tmpfs,target=/run --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome make install-unit-tests
107+
RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome make install-unit-tests
108108

109109
# This just does syntax checking
110110
FROM buildroot as validate
111-
RUN --network=none --mount=type=tmpfs,target=/run --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome make validate
111+
RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome make validate
112112

113113
# Common base for final images: configures variant, rootfs, and injects extra content
114114
FROM base as final-common
115115
ARG variant
116-
RUN --network=none --mount=type=tmpfs,target=/run \
116+
RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \
117117
--mount=type=bind,from=packaging,src=/,target=/run/packaging \
118118
--mount=type=bind,from=sdboot-content,src=/,target=/run/sdboot-content \
119119
--mount=type=bind,from=sdboot-signed,src=/,target=/run/sdboot-signed \
120120
/run/packaging/configure-variant "${variant}"
121121
ARG rootfs=""
122-
RUN --network=none --mount=type=tmpfs,target=/run \
122+
RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \
123123
--mount=type=bind,from=packaging,src=/,target=/run/packaging \
124124
/run/packaging/configure-rootfs "${variant}" "${rootfs}"
125125
COPY --from=packaging /usr-extras/ /usr/
@@ -128,10 +128,14 @@ COPY --from=packaging /usr-extras/ /usr/
128128
# Use with: podman build --target=final --build-context packages=path/to/packages
129129
# We use --build-context instead of -v to avoid volume mount stubs leaking into /run.
130130
FROM final-common as final
131-
RUN --network=none --mount=type=tmpfs,target=/run \
131+
RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \
132132
--mount=type=bind,from=packaging,src=/,target=/run/packaging \
133133
--mount=type=bind,from=packages,src=/,target=/run/packages \
134134
/run/packaging/install-rpm-and-setup /run/packages
135-
# Use tmpfs on /run to hide any content created by podman for DNS resolution
136-
# (e.g., /run/systemd/resolve/stub-resolv.conf on Ubuntu hosts)
137-
RUN --network=none --mount=type=tmpfs,target=/run bootc container lint --fatal-warnings
135+
# lint: allow non-tmpfs
136+
RUN --network=none <<EORUN
137+
set -xeuo pipefail
138+
# workaround for https://github.com/containers/buildah/pull/6233
139+
rm -vrf /run/systemd
140+
bootc container lint --fatal-warnings
141+
EORUN

Dockerfile.cfsuki

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ ARG base=localhost/bootc
33
FROM $base AS base
44

55
FROM base as kernel
6-
# Use tmpfs on /run to prevent podman's DNS resolver files from being committed
7-
RUN --mount=type=tmpfs,target=/run <<EORUN
6+
# Use tmpfs on /run and /tmp to prevent podman's DNS resolver files from being committed
7+
RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp <<EORUN
88
set -xeuo pipefail
99
. /usr/lib/os-release
1010
case $ID in
@@ -18,7 +18,7 @@ dnf -y install systemd-ukify sbsigntools
1818
EORUN
1919
# Must be passed
2020
ARG COMPOSEFS_FSVERITY
21-
RUN --network=none --mount=type=tmpfs,target=/run \
21+
RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \
2222
--mount=type=secret,id=secureboot_key \
2323
--mount=type=secret,id=secureboot_cert \
2424
--mount=type=bind,from=base,src=/,target=/target \
@@ -47,7 +47,7 @@ RUN --network=none --mount=type=tmpfs,target=/run \
4747
EOF
4848

4949
FROM base as final
50-
RUN --network=none --mount=type=tmpfs,target=/run \
50+
RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \
5151
--mount=type=bind,from=kernel,src=/,target=/run/kernel <<EOF
5252
set -xeuo pipefail
5353
kver=$(cd /usr/lib/modules && echo *)
@@ -61,8 +61,13 @@ rm -v /usr/lib/modules/${kver}/{vmlinuz,initramfs.img}
6161
# Symlink into the /usr/lib/modules location
6262
ln -sr $target /usr/lib/modules/${kver}/$(basename $kver.efi)
6363
EOF
64-
# Use tmpfs on /run to ensure lint sees empty /run (hiding any podman-created content)
65-
RUN --network=none --mount=type=tmpfs,target=/run bootc container lint --fatal-warnings
64+
# lint: allow non-tmpfs
65+
RUN --network=none <<EORUN
66+
set -xeuo pipefail
67+
# workaround for https://github.com/containers/buildah/pull/6233
68+
rm -vrf /run/systemd
69+
bootc container lint --fatal-warnings
70+
EORUN
6671

6772
FROM base as final-final
6873
COPY --from=final /boot /boot

crates/xtask/src/buildsys.rs

Lines changed: 68 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const DOCKERFILE_NETWORK_CUTOFF: &str = "external dependency cutoff point";
1313
///
1414
/// - Reproducible builds for the RPM
1515
/// - Dockerfile network isolation after cutoff point
16-
/// - Dockerfile tmpfs on /run for all RUN instructions
16+
/// - Dockerfile tmpfs on /run and /tmp for all RUN instructions
1717
#[context("Checking build system")]
1818
pub fn check_buildsys(sh: &Shell, dockerfile_path: &Utf8Path) -> Result<()> {
1919
check_package_reproducibility(sh)?;
@@ -75,11 +75,16 @@ fn check_dockerfile_rules(dockerfile_path: &Utf8Path) -> Result<()> {
7575
}
7676

7777
const RUN_NETWORK_NONE: &str = "RUN --network=none";
78-
const RUN_TMPFS: &str = "--mount=type=tmpfs,target=/run";
78+
const RUN_TMPFS_RUN: &str = "--mount=type=tmpfs,target=/run";
79+
const RUN_TMPFS_TMP: &str = "--mount=type=tmpfs,target=/tmp";
80+
const ALLOW_NON_TMPFS: &str = "# lint: allow non-tmpfs";
7981

8082
/// Verify Dockerfile rules:
81-
/// - All RUN instructions must include `--mount=type=tmpfs,target=/run` to prevent
82-
/// podman's DNS resolver files from leaking into the image
83+
/// - All RUN instructions must include `--mount=type=tmpfs,target=/run` and
84+
/// `--mount=type=tmpfs,target=/tmp` to prevent podman's DNS resolver files
85+
/// and temporary files from leaking into the image
86+
/// - A comment `# lint: allow non-tmpfs` on the preceding line exempts a RUN
87+
/// instruction from the tmpfs requirement
8388
/// - After the network cutoff, all RUN instructions must start with `--network=none`
8489
///
8590
/// Returns Ok(()) if all RUN instructions comply, or an error listing violations.
@@ -96,20 +101,38 @@ pub fn verify_dockerfile_rules(dockerfile: &str) -> Result<()> {
96101
})?;
97102

98103
let mut errors = Vec::new();
104+
let mut skip_tmpfs_check = false;
99105

100106
for (idx, line) in dockerfile.lines().enumerate() {
101107
let line_num = idx + 1; // 1-based line numbers
102108
let trimmed = line.trim();
103109

110+
// Check for the allow comment directive
111+
if trimmed.starts_with(ALLOW_NON_TMPFS) {
112+
skip_tmpfs_check = true;
113+
continue;
114+
}
115+
104116
// Check if this is a RUN instruction
105117
if trimmed.starts_with("RUN ") {
106-
// All RUN instructions must include tmpfs mount on /run
107-
if !trimmed.contains(RUN_TMPFS) {
108-
errors.push(format!(
109-
" line {}: RUN instruction must include `{}`",
110-
line_num, RUN_TMPFS
111-
));
118+
if !skip_tmpfs_check {
119+
// All RUN instructions must include tmpfs mount on /run
120+
if !trimmed.contains(RUN_TMPFS_RUN) {
121+
errors.push(format!(
122+
" line {}: RUN instruction must include `{}`",
123+
line_num, RUN_TMPFS_RUN
124+
));
125+
}
126+
127+
// All RUN instructions must include tmpfs mount on /tmp
128+
if !trimmed.contains(RUN_TMPFS_TMP) {
129+
errors.push(format!(
130+
" line {}: RUN instruction must include `{}`",
131+
line_num, RUN_TMPFS_TMP
132+
));
133+
}
112134
}
135+
skip_tmpfs_check = false;
113136

114137
// After cutoff, must start with exactly "RUN --network=none"
115138
if idx > cutoff_line && !trimmed.starts_with(RUN_NETWORK_NONE) {
@@ -139,35 +162,57 @@ mod tests {
139162
fn test_dockerfile_rules_valid() {
140163
let dockerfile = r#"
141164
FROM base
142-
RUN --mount=type=tmpfs,target=/run echo "before cutoff, network allowed"
165+
RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp echo "before cutoff, network allowed"
143166
# external dependency cutoff point
144-
RUN --network=none --mount=type=tmpfs,target=/run echo "good"
145-
RUN --network=none --mount=type=tmpfs,target=/run --mount=type=bind,from=foo,target=/bar some-command
167+
RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp echo "good"
168+
RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp --mount=type=bind,from=foo,target=/bar some-command
169+
# lint: allow non-tmpfs
170+
RUN --network=none bootc container lint --fatal-warnings
146171
"#;
147172
verify_dockerfile_rules(dockerfile).unwrap();
148173
}
149174

150175
#[test]
151-
fn test_dockerfile_rules_missing_tmpfs_before_cutoff() {
176+
fn test_dockerfile_rules_missing_tmpfs_run_before_cutoff() {
152177
let dockerfile = r#"
153178
FROM base
154-
RUN echo "bad - missing tmpfs"
179+
RUN --mount=type=tmpfs,target=/tmp echo "bad - missing /run tmpfs"
155180
# external dependency cutoff point
156-
RUN --network=none --mount=type=tmpfs,target=/run echo "good"
181+
RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp echo "good"
157182
"#;
158183
let err = verify_dockerfile_rules(dockerfile).unwrap_err();
159184
let msg = err.to_string();
160185
assert!(msg.contains("line 3"), "error should mention line 3: {msg}");
161-
assert!(msg.contains("tmpfs"), "error should mention tmpfs: {msg}");
186+
assert!(
187+
msg.contains("target=/run"),
188+
"error should mention target=/run: {msg}"
189+
);
190+
}
191+
192+
#[test]
193+
fn test_dockerfile_rules_missing_tmpfs_tmp_before_cutoff() {
194+
let dockerfile = r#"
195+
FROM base
196+
RUN --mount=type=tmpfs,target=/run echo "bad - missing /tmp tmpfs"
197+
# external dependency cutoff point
198+
RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp echo "good"
199+
"#;
200+
let err = verify_dockerfile_rules(dockerfile).unwrap_err();
201+
let msg = err.to_string();
202+
assert!(msg.contains("line 3"), "error should mention line 3: {msg}");
203+
assert!(
204+
msg.contains("target=/tmp"),
205+
"error should mention target=/tmp: {msg}"
206+
);
162207
}
163208

164209
#[test]
165210
fn test_dockerfile_rules_missing_network_flag_after_cutoff() {
166211
let dockerfile = r#"
167212
FROM base
168-
RUN --mount=type=tmpfs,target=/run echo "before cutoff"
213+
RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp echo "before cutoff"
169214
# external dependency cutoff point
170-
RUN --mount=type=tmpfs,target=/run echo "bad - missing network flag"
215+
RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp echo "bad - missing network flag"
171216
"#;
172217
let err = verify_dockerfile_rules(dockerfile).unwrap_err();
173218
let msg = err.to_string();
@@ -182,9 +227,9 @@ RUN --mount=type=tmpfs,target=/run echo "bad - missing network flag"
182227
fn test_dockerfile_rules_missing_tmpfs_after_cutoff() {
183228
let dockerfile = r#"
184229
FROM base
185-
RUN --mount=type=tmpfs,target=/run echo "before cutoff"
230+
RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp echo "before cutoff"
186231
# external dependency cutoff point
187-
RUN --network=none echo "bad - missing tmpfs"
232+
RUN --network=none echo "bad - missing both tmpfs"
188233
"#;
189234
let err = verify_dockerfile_rules(dockerfile).unwrap_err();
190235
let msg = err.to_string();
@@ -197,9 +242,9 @@ RUN --network=none echo "bad - missing tmpfs"
197242
// --network=none must come immediately after RUN
198243
let dockerfile = r#"
199244
FROM base
200-
RUN --mount=type=tmpfs,target=/run echo "before cutoff"
245+
RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp echo "before cutoff"
201246
# external dependency cutoff point
202-
RUN --mount=type=tmpfs,target=/run --network=none echo "bad - network flag not first"
247+
RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp --network=none echo "bad - network flag not first"
203248
"#;
204249
let err = verify_dockerfile_rules(dockerfile).unwrap_err();
205250
let msg = err.to_string();

0 commit comments

Comments
 (0)