Skip to content

Commit 2ed97d4

Browse files
committed
lints: Check for invalid /etc/hostname and /etc/resolv.conf
Detect problems from containers/buildah#4242 or similar. As part of this, add new infrastructure logic for lints that only operate on non-running roots (we expect these are mounted/written at runtime). Signed-off-by: Colin Walters <[email protected]>
1 parent 3db492f commit 2ed97d4

File tree

2 files changed

+69
-3
lines changed

2 files changed

+69
-3
lines changed

lib/src/cli.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1053,8 +1053,14 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
10531053
} else {
10541054
lints::WarningDisposition::AllowWarnings
10551055
};
1056+
let root_type = if rootfs == "/" {
1057+
lints::RootType::Running
1058+
} else {
1059+
lints::RootType::Alternative
1060+
};
1061+
10561062
let root = &Dir::open_ambient_dir(rootfs, cap_std::ambient_authority())?;
1057-
lints::lint(root, warnings, std::io::stdout().lock())?;
1063+
lints::lint(root, warnings, root_type, std::io::stdout().lock())?;
10581064
Ok(())
10591065
}
10601066
},

lib/src/lints.rs

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ pub(crate) enum WarningDisposition {
7676
FatalWarnings,
7777
}
7878

79+
#[derive(Debug, Copy, Clone, Serialize, PartialEq, Eq)]
80+
pub(crate) enum RootType {
81+
Running,
82+
Alternative,
83+
}
84+
7985
#[derive(Debug, Serialize)]
8086
#[serde(rename_all = "kebab-case")]
8187
struct Lint {
@@ -85,6 +91,9 @@ struct Lint {
8591
#[serde(skip)]
8692
f: LintFn,
8793
description: &'static str,
94+
// Set if this only applies to a specific root type.
95+
#[serde(skip_serializing_if = "Option::is_none")]
96+
root_type: Option<RootType>,
8897
}
8998

9099
impl Lint {
@@ -98,6 +107,7 @@ impl Lint {
98107
ty: LintType::Fatal,
99108
f: f,
100109
description: description,
110+
root_type: None,
101111
}
102112
}
103113

@@ -111,6 +121,7 @@ impl Lint {
111121
ty: LintType::Warning,
112122
f: f,
113123
description: description,
124+
root_type: None,
114125
}
115126
}
116127
}
@@ -128,13 +139,22 @@ pub(crate) fn lint_list(output: impl std::io::Write) -> Result<()> {
128139
pub(crate) fn lint(
129140
root: &Dir,
130141
warning_disposition: WarningDisposition,
142+
root_type: RootType,
131143
mut output: impl std::io::Write,
132144
) -> Result<()> {
133145
let mut fatal = 0usize;
134146
let mut warnings = 0usize;
135147
let mut passed = 0usize;
148+
let mut skipped = 0usize;
136149
for lint in LINTS {
137150
let name = lint.name;
151+
152+
if let Some(lint_root_type) = lint.root_type {
153+
if lint_root_type != root_type {
154+
skipped += 1;
155+
}
156+
}
157+
138158
let r = match (lint.f)(&root) {
139159
Ok(r) => r,
140160
Err(e) => anyhow::bail!("Unexpected runtime error running lint {name}: {e}"),
@@ -158,6 +178,9 @@ pub(crate) fn lint(
158178
}
159179
}
160180
writeln!(output, "Checks passed: {passed}")?;
181+
if skipped > 0 {
182+
writeln!(output, "Checks skipped: {skipped}")?;
183+
}
161184
let fatal = if matches!(warning_disposition, WarningDisposition::FatalWarnings) {
162185
fatal + warnings
163186
} else {
@@ -187,6 +210,30 @@ fn check_var_run(root: &Dir) -> LintResult {
187210
lint_ok()
188211
}
189212

213+
#[distributed_slice(LINTS)]
214+
static LINT_BUILDAH_INJECTED: Lint = Lint {
215+
name: "buildah-injected",
216+
description: indoc::indoc! { "
217+
Check for an invalid /etc/hostname or /etc/resolv.conf that may have been injected by
218+
a container build system." },
219+
ty: LintType::Warning,
220+
f: check_buildah_injected,
221+
// This one doesn't make sense to run looking at the running root,
222+
// because we do expect /etc/hostname to be injected as
223+
root_type: Some(RootType::Alternative),
224+
};
225+
fn check_buildah_injected(root: &Dir) -> LintResult {
226+
const RUNTIME_INJECTED: &[&str] = &["etc/hostname", "etc/resolv.conf"];
227+
for ent in RUNTIME_INJECTED {
228+
if let Some(meta) = root.symlink_metadata_optional(ent)? {
229+
if meta.is_file() && meta.size() == 0 {
230+
return lint_err(format!("/{ent} is an empty file; this may have been synthesized by a container runtime."));
231+
}
232+
}
233+
}
234+
lint_ok()
235+
}
236+
190237
#[distributed_slice(LINTS)]
191238
static LINT_ETC_USRUSETC: Lint = Lint::new_fatal(
192239
"etc-usretc",
@@ -462,10 +509,11 @@ mod tests {
462509
let root = &passing_fixture()?;
463510
let mut out = Vec::new();
464511
let warnings = WarningDisposition::FatalWarnings;
465-
lint(root, warnings, &mut out).unwrap();
512+
let root_type = RootType::Running;
513+
lint(root, warnings, root_type, &mut out).unwrap();
466514
root.create_dir_all("var/run/foo")?;
467515
let mut out = Vec::new();
468-
assert!(lint(root, warnings, &mut out).is_err());
516+
assert!(lint(root, warnings, root_type, &mut out).is_err());
469517
Ok(())
470518
}
471519

@@ -639,6 +687,18 @@ mod tests {
639687
Ok(())
640688
}
641689

690+
#[test]
691+
fn test_buildah_injected() -> Result<()> {
692+
let td = fixture()?;
693+
td.create_dir("etc")?;
694+
assert!(check_buildah_injected(&td).unwrap().is_ok());
695+
td.write("etc/hostname", b"")?;
696+
assert!(check_buildah_injected(&td).unwrap().is_err());
697+
td.write("etc/hostname", b"some static hostname")?;
698+
assert!(check_buildah_injected(&td).unwrap().is_ok());
699+
Ok(())
700+
}
701+
642702
#[test]
643703
fn test_list() {
644704
let mut r = Vec::new();

0 commit comments

Comments
 (0)