Skip to content

Commit 4413c56

Browse files
committed
kernel_cmdline: More improvements and fixes
- Removed `From<bytes::Parameter>` implementation for `utf8::Parameter` and similar for `utf8::ParameterKey`. This was public and would allow end-users to construct utf8 parameters from non-utf8 data. Replaced internally with `from_bytes` in the places where we know we can safely convert known-UTF-8 data. - Added `TryFrom<bytes::Paramter>` implementation for `utf8::Parameter` to allow checked conversions, plus tests. - Updated `find_root_args_to_inherit` in bootc to use these improvements. Notably bootc will now allow non-UTF8 data in the kernel cmdline, *unless* it occurs in parameters that bootc is explicitly looking for. - Added more tests to `find_root_args_to_inherit` to validate expected functionality with non-UTF-8 data. Signed-off-by: John Eckersberg <jeckersb@redhat.com>
1 parent 5454cec commit 4413c56

File tree

3 files changed

+116
-28
lines changed

3 files changed

+116
-28
lines changed

crates/kernel_cmdline/src/lib.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,3 @@
1212
1313
pub mod bytes;
1414
pub mod utf8;
15-
16-
/// This is used by dracut.
17-
pub const INITRD_ARG_PREFIX: &str = "rd.";
18-
/// The kernel argument for configuring the rootfs flags.
19-
pub const ROOTFLAGS: &str = "rootflags";

crates/kernel_cmdline/src/utf8.rs

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
//! This module provides functionality for parsing and working with kernel command line
44
//! arguments, supporting both key-only switches and key-value pairs with proper quote handling.
55
6+
use std::ops::Deref;
7+
68
use crate::bytes;
79

810
use anyhow::Result;
@@ -34,7 +36,7 @@ impl<'a> Iterator for CmdlineIter<'a> {
3436
type Item = Parameter<'a>;
3537

3638
fn next(&mut self) -> Option<Self::Item> {
37-
self.0.next().map(Into::into)
39+
self.0.next().map(Parameter::from_bytes)
3840
}
3941
}
4042

@@ -122,15 +124,19 @@ impl<'a> std::ops::Deref for ParameterKey<'a> {
122124
}
123125
}
124126

125-
impl<'a> From<&'a str> for ParameterKey<'a> {
126-
fn from(input: &'a str) -> Self {
127-
Self(bytes::ParameterKey(input.as_bytes()))
127+
impl<'a> ParameterKey<'a> {
128+
/// Construct a utf8::ParameterKey from a bytes::ParameterKey
129+
///
130+
/// This is non-public and should only be used when the underlying
131+
/// bytes are known to be valid UTF-8.
132+
fn from_bytes(input: bytes::ParameterKey<'a>) -> Self {
133+
Self(input)
128134
}
129135
}
130136

131-
impl<'a> From<bytes::ParameterKey<'a>> for ParameterKey<'a> {
132-
fn from(input: bytes::ParameterKey<'a>) -> Self {
133-
Self(input)
137+
impl<'a> From<&'a str> for ParameterKey<'a> {
138+
fn from(input: &'a str) -> Self {
139+
Self(bytes::ParameterKey(input.as_bytes()))
134140
}
135141
}
136142

@@ -181,9 +187,17 @@ impl<'a> Parameter<'a> {
181187
}
182188
}
183189

190+
/// Construct a utf8::Parameter from a bytes::Parameter
191+
///
192+
/// This is non-public and should only be used when the underlying
193+
/// bytes are known to be valid UTF-8.
194+
fn from_bytes(bytes: bytes::Parameter<'a>) -> Self {
195+
Self(bytes)
196+
}
197+
184198
/// Returns the key part of the parameter
185199
pub fn key(&'a self) -> ParameterKey<'a> {
186-
self.0.key().into()
200+
ParameterKey::from_bytes(self.0.key())
187201
}
188202

189203
/// Returns the optional value part of the parameter
@@ -196,9 +210,21 @@ impl<'a> Parameter<'a> {
196210
}
197211
}
198212

199-
impl<'a> From<bytes::Parameter<'a>> for Parameter<'a> {
200-
fn from(bytes: bytes::Parameter<'a>) -> Self {
201-
Self(bytes)
213+
impl<'a> TryFrom<bytes::Parameter<'a>> for Parameter<'a> {
214+
type Error = anyhow::Error;
215+
216+
fn try_from(bytes: bytes::Parameter<'a>) -> Result<Self, Self::Error> {
217+
if str::from_utf8(bytes.key().deref()).is_err() {
218+
anyhow::bail!("Parameter key is not valid UTF-8");
219+
}
220+
221+
if let Some(value) = bytes.value() {
222+
if str::from_utf8(value).is_err() {
223+
anyhow::bail!("Parameter value is not valid UTF-8");
224+
}
225+
}
226+
227+
Ok(Self(bytes))
202228
}
203229
}
204230

@@ -331,6 +357,37 @@ mod tests {
331357
assert_ne!(switch, keyvalue);
332358
}
333359

360+
#[test]
361+
fn test_parameter_tryfrom() {
362+
// ok switch
363+
let p = bytes::Parameter::parse(b"foo").0.unwrap();
364+
let utf = Parameter::try_from(p).unwrap();
365+
assert_eq!(utf.key(), "foo".into());
366+
assert_eq!(utf.value(), None);
367+
368+
// ok key/value
369+
let p = bytes::Parameter::parse(b"foo=bar").0.unwrap();
370+
let utf = Parameter::try_from(p).unwrap();
371+
assert_eq!(utf.key(), "foo".into());
372+
assert_eq!(utf.value(), Some("bar".into()));
373+
374+
// bad switch
375+
let p = bytes::Parameter::parse(b"f\xffoo").0.unwrap();
376+
let e = Parameter::try_from(p);
377+
assert_eq!(
378+
e.unwrap_err().to_string(),
379+
"Parameter key is not valid UTF-8"
380+
);
381+
382+
// bad key/value
383+
let p = bytes::Parameter::parse(b"foo=b\xffar").0.unwrap();
384+
let e = Parameter::try_from(p);
385+
assert_eq!(
386+
e.unwrap_err().to_string(),
387+
"Parameter value is not valid UTF-8"
388+
);
389+
}
390+
334391
#[test]
335392
fn test_kargs_simple() {
336393
// example taken lovingly from:

crates/lib/src/install.rs

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ use crate::spec::ImageReference;
6262
use crate::store::Storage;
6363
use crate::task::Task;
6464
use crate::utils::sigpolicy_from_opt;
65-
use bootc_kernel_cmdline::utf8::Cmdline;
65+
use bootc_kernel_cmdline::{bytes, utf8};
6666
use bootc_mount::Filesystem;
6767

6868
/// The toplevel boot directory
@@ -83,6 +83,10 @@ const SELINUXFS: &str = "/sys/fs/selinux";
8383
/// The mount path for uefi
8484
const EFIVARFS: &str = "/sys/firmware/efi/efivars";
8585
pub(crate) const ARCH_USES_EFI: bool = cfg!(any(target_arch = "x86_64", target_arch = "aarch64"));
86+
/// This is used by dracut.
87+
pub const INITRD_ARG_PREFIX: &[u8] = b"rd.";
88+
/// The kernel argument for configuring the rootfs flags.
89+
pub const ROOTFLAGS: &[u8] = b"rootflags";
8690

8791
const DEFAULT_REPO_CONFIG: &[(&str, &str)] = &[
8892
// Default to avoiding grub2-mkconfig etc.
@@ -1653,18 +1657,26 @@ struct RootMountInfo {
16531657

16541658
/// Discover how to mount the root filesystem, using existing kernel arguments and information
16551659
/// about the root mount.
1656-
fn find_root_args_to_inherit(cmdline: &Cmdline, root_info: &Filesystem) -> Result<RootMountInfo> {
1660+
fn find_root_args_to_inherit(
1661+
cmdline: &bytes::Cmdline,
1662+
root_info: &Filesystem,
1663+
) -> Result<RootMountInfo> {
16571664
// If we have a root= karg, then use that
1658-
let (mount_spec, kargs) = if let Some(root) = cmdline.value_of("root") {
1659-
let rootflags = cmdline.find(bootc_kernel_cmdline::ROOTFLAGS);
1660-
let inherit_kargs = cmdline.find_all_starting_with(bootc_kernel_cmdline::INITRD_ARG_PREFIX);
1665+
let root = cmdline
1666+
.find("root")
1667+
.map(utf8::Parameter::try_from)
1668+
.transpose()?
1669+
.and_then(|p| p.value().map(|p| p.to_string()));
1670+
let (mount_spec, kargs) = if let Some(root) = root {
1671+
let rootflags = cmdline.find(ROOTFLAGS);
1672+
let inherit_kargs = cmdline.find_all_starting_with(INITRD_ARG_PREFIX);
16611673
(
1662-
root.to_owned(),
1674+
root,
16631675
rootflags
16641676
.into_iter()
16651677
.chain(inherit_kargs)
1666-
.map(|p| p.to_string())
1667-
.collect(),
1678+
.map(|p| utf8::Parameter::try_from(p).map(|p| p.to_string()))
1679+
.collect::<Result<Vec<_>, _>>()?,
16681680
)
16691681
} else {
16701682
let uuid = root_info
@@ -1832,7 +1844,7 @@ pub(crate) async fn install_to_filesystem(
18321844
}
18331845
} else if targeting_host_root {
18341846
// In the to-existing-root case, look at /proc/cmdline
1835-
let cmdline = Cmdline::from_proc()?;
1847+
let cmdline = bytes::Cmdline::from_proc()?;
18361848
find_root_args_to_inherit(&cmdline, &inspect)?
18371849
} else {
18381850
// Otherwise, gather metadata from the provided root and use its provided UUID as a
@@ -2085,18 +2097,42 @@ mod tests {
20852097
uuid: Some("965eb3c7-5a3f-470d-aaa2-1bcf04334bc6".into()),
20862098
children: None,
20872099
};
2088-
let kargs = Cmdline::from("");
2100+
let kargs = bytes::Cmdline::from("");
20892101
let r = find_root_args_to_inherit(&kargs, &inspect).unwrap();
20902102
assert_eq!(r.mount_spec, "UUID=965eb3c7-5a3f-470d-aaa2-1bcf04334bc6");
20912103

2092-
let kargs =
2093-
Cmdline::from("root=/dev/mapper/root rw someother=karg rd.lvm.lv=root systemd.debug=1");
2104+
let kargs = bytes::Cmdline::from(
2105+
"root=/dev/mapper/root rw someother=karg rd.lvm.lv=root systemd.debug=1",
2106+
);
20942107

20952108
// In this case we take the root= from the kernel cmdline
20962109
let r = find_root_args_to_inherit(&kargs, &inspect).unwrap();
20972110
assert_eq!(r.mount_spec, "/dev/mapper/root");
20982111
assert_eq!(r.kargs.len(), 1);
20992112
assert_eq!(r.kargs[0], "rd.lvm.lv=root");
2113+
2114+
// non-UTF8 data in non-essential parts of the cmdline should be ignored
2115+
let kargs = bytes::Cmdline::from(
2116+
b"root=/dev/mapper/root rw non-utf8=\xff rd.lvm.lv=root systemd.debug=1",
2117+
);
2118+
let r = find_root_args_to_inherit(&kargs, &inspect).unwrap();
2119+
assert_eq!(r.mount_spec, "/dev/mapper/root");
2120+
assert_eq!(r.kargs.len(), 1);
2121+
assert_eq!(r.kargs[0], "rd.lvm.lv=root");
2122+
2123+
// non-UTF8 data in `root` should fail
2124+
let kargs = bytes::Cmdline::from(
2125+
b"root=/dev/mapper/ro\xffot rw non-utf8=\xff rd.lvm.lv=root systemd.debug=1",
2126+
);
2127+
let r = find_root_args_to_inherit(&kargs, &inspect);
2128+
assert!(r.is_err());
2129+
2130+
// non-UTF8 data in `rd.` should fail
2131+
let kargs = bytes::Cmdline::from(
2132+
b"root=/dev/mapper/root rw non-utf8=\xff rd.lvm.lv=ro\xffot systemd.debug=1",
2133+
);
2134+
let r = find_root_args_to_inherit(&kargs, &inspect);
2135+
assert!(r.is_err());
21002136
}
21012137

21022138
// As this is a unit test we don't try to test mountpoints, just verify

0 commit comments

Comments
 (0)