Skip to content

Commit 47a1984

Browse files
Traistaru Andrei Cristianandreeaflorescu
authored andcommitted
Changing as_str into as_cstring
Changing as_str() into as_cstring() in order to retrieve a CString (which is a null terminated string) from the Cmdline struct. We found a bug introduced by the following PR: #72 This bug was caused by the fact that method load_cmdline() was changed to receive a Cmdline instead of a CStr. That leads to the call of the as_str() method from the Cmdline to get the representation of the kernel command line. The method as_str() from Cmdline returns a plain string from Rust that is not null terminated by default. In this commit, we kept the load_cmdline() method to receive a Cmdline but converted the as_str() method into as_cstring() that returns a null terminated string now. Signed-off-by: Traistaru Andrei Cristian <atc@amazon.com>
1 parent 8e31c6a commit 47a1984

File tree

2 files changed

+99
-37
lines changed

2 files changed

+99
-37
lines changed

src/cmdline/mod.rs

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
//
99
//! Helper for creating valid kernel command line strings.
1010
11+
use std::ffi::CString;
1112
use std::fmt;
1213
use std::result;
1314

@@ -16,6 +17,8 @@ use vm_memory::{Address, GuestAddress, GuestUsize};
1617
/// The error type for command line building operations.
1718
#[derive(Debug, PartialEq, Eq)]
1819
pub enum Error {
20+
/// Null terminator identified in the command line.
21+
NullTerminator,
1922
/// Operation would have resulted in a non-printable ASCII character.
2023
InvalidAscii,
2124
/// Invalid device passed to the kernel command line builder.
@@ -35,6 +38,9 @@ pub enum Error {
3538
impl fmt::Display for Error {
3639
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3740
match *self {
41+
Error::NullTerminator => {
42+
write!(f, "Null terminator detected in the command line structure.")
43+
}
3844
Error::InvalidAscii => write!(f, "String contains a non-printable ASCII character."),
3945
Error::InvalidDevice(ref dev) => write!(
4046
f,
@@ -156,11 +162,9 @@ impl Cmdline {
156162
///
157163
/// ```rust
158164
/// # use linux_loader::cmdline::*;
159-
/// # use std::ffi::CString;
160165
/// let mut cl = Cmdline::new(100);
161166
/// cl.insert("foo", "bar");
162-
/// let cl_cstring = CString::new(cl).unwrap();
163-
/// assert_eq!(cl_cstring.to_str().unwrap(), "foo=bar");
167+
/// assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"foo=bar\0");
164168
/// ```
165169
pub fn insert<T: AsRef<str>>(&mut self, key: T, val: T) -> Result<()> {
166170
let k = key.as_ref();
@@ -248,18 +252,23 @@ impl Cmdline {
248252
Ok(())
249253
}
250254

251-
/// Returns the string representation of the command line without the nul terminator.
255+
/// Returns a C compatible representation of the command line
256+
/// The Linux kernel expects a null terminated cmdline according to the source:
257+
/// https://elixir.bootlin.com/linux/v5.10.139/source/kernel/params.c#L179
258+
///
259+
/// To get bytes of the cmdline to be written in guest's memory (including the
260+
/// null terminator) from this representation, use CString::as_bytes_with_nul()
252261
///
253262
/// # Examples
254263
///
255264
/// ```rust
256265
/// # use linux_loader::cmdline::*;
257266
/// let mut cl = Cmdline::new(10);
258267
/// cl.insert_str("foobar");
259-
/// assert_eq!(cl.as_str(), "foobar");
268+
/// assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"foobar\0");
260269
/// ```
261-
pub fn as_str(&self) -> &str {
262-
self.line.as_str()
270+
pub fn as_cstring(&self) -> Result<CString> {
271+
CString::new(self.line.to_string()).map_err(|_| Error::NullTerminator)
263272
}
264273

265274
/// Adds a virtio MMIO device to the kernel command line.
@@ -356,9 +365,12 @@ mod tests {
356365
#[test]
357366
fn test_insert_hello_world() {
358367
let mut cl = Cmdline::new(100);
359-
assert_eq!(cl.as_str(), "");
368+
assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"\0");
360369
assert!(cl.insert("hello", "world").is_ok());
361-
assert_eq!(cl.as_str(), "hello=world");
370+
assert_eq!(
371+
cl.as_cstring().unwrap().as_bytes_with_nul(),
372+
b"hello=world\0"
373+
);
362374

363375
let s = CString::new(cl).expect("failed to create CString from Cmdline");
364376
assert_eq!(s, CString::new("hello=world").unwrap());
@@ -369,7 +381,10 @@ mod tests {
369381
let mut cl = Cmdline::new(100);
370382
assert!(cl.insert("hello", "world").is_ok());
371383
assert!(cl.insert("foo", "bar").is_ok());
372-
assert_eq!(cl.as_str(), "hello=world foo=bar");
384+
assert_eq!(
385+
cl.as_cstring().unwrap().as_bytes_with_nul(),
386+
b"hello=world foo=bar\0"
387+
);
373388
}
374389

375390
#[test]
@@ -379,7 +394,7 @@ mod tests {
379394
assert_eq!(cl.insert("a", "b "), Err(Error::HasSpace));
380395
assert_eq!(cl.insert("a ", "b "), Err(Error::HasSpace));
381396
assert_eq!(cl.insert(" a", "b"), Err(Error::HasSpace));
382-
assert_eq!(cl.as_str(), "");
397+
assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"\0");
383398
}
384399

385400
#[test]
@@ -390,25 +405,28 @@ mod tests {
390405
assert_eq!(cl.insert("a=", "b "), Err(Error::HasEquals));
391406
assert_eq!(cl.insert("=a", "b"), Err(Error::HasEquals));
392407
assert_eq!(cl.insert("a", "=b"), Err(Error::HasEquals));
393-
assert_eq!(cl.as_str(), "");
408+
assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"\0");
394409
}
395410

396411
#[test]
397412
fn test_insert_emoji() {
398413
let mut cl = Cmdline::new(100);
399414
assert_eq!(cl.insert("heart", "💖"), Err(Error::InvalidAscii));
400415
assert_eq!(cl.insert("💖", "love"), Err(Error::InvalidAscii));
401-
assert_eq!(cl.as_str(), "");
416+
assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"\0");
402417
}
403418

404419
#[test]
405420
fn test_insert_string() {
406421
let mut cl = Cmdline::new(13);
407-
assert_eq!(cl.as_str(), "");
422+
assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"\0");
408423
assert!(cl.insert_str("noapic").is_ok());
409-
assert_eq!(cl.as_str(), "noapic");
424+
assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"noapic\0");
410425
assert!(cl.insert_str("nopci").is_ok());
411-
assert_eq!(cl.as_str(), "noapic nopci");
426+
assert_eq!(
427+
cl.as_cstring().unwrap().as_bytes_with_nul(),
428+
b"noapic nopci\0"
429+
);
412430
}
413431

414432
#[test]
@@ -420,7 +438,7 @@ mod tests {
420438
assert!(cl.insert("a", "b").is_ok());
421439
assert_eq!(cl.insert("a", "b"), Err(Error::TooLarge));
422440
assert_eq!(cl.insert_str("a"), Err(Error::TooLarge));
423-
assert_eq!(cl.as_str(), "a=b");
441+
assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"a=b\0");
424442

425443
let mut cl = Cmdline::new(10);
426444
assert!(cl.insert("ab", "ba").is_ok()); // adds 5 length
@@ -445,25 +463,37 @@ mod tests {
445463
.add_virtio_mmio_device(1, GuestAddress(0), 1, None)
446464
.is_ok());
447465
let mut expected_str = "virtio_mmio.device=1@0x0:1".to_string();
448-
assert_eq!(cl.as_str(), &expected_str);
466+
assert_eq!(
467+
cl.as_cstring().unwrap(),
468+
CString::new(expected_str.as_bytes()).unwrap()
469+
);
449470

450471
assert!(cl
451472
.add_virtio_mmio_device(2 << 10, GuestAddress(0x100), 2, None)
452473
.is_ok());
453474
expected_str.push_str(" virtio_mmio.device=2K@0x100:2");
454-
assert_eq!(cl.as_str(), &expected_str);
475+
assert_eq!(
476+
cl.as_cstring().unwrap(),
477+
CString::new(expected_str.as_bytes()).unwrap()
478+
);
455479

456480
assert!(cl
457481
.add_virtio_mmio_device(3 << 20, GuestAddress(0x1000), 3, None)
458482
.is_ok());
459483
expected_str.push_str(" virtio_mmio.device=3M@0x1000:3");
460-
assert_eq!(cl.as_str(), &expected_str);
484+
assert_eq!(
485+
cl.as_cstring().unwrap(),
486+
CString::new(expected_str.as_bytes()).unwrap()
487+
);
461488

462489
assert!(cl
463490
.add_virtio_mmio_device(4 << 30, GuestAddress(0x0001_0000), 4, Some(42))
464491
.is_ok());
465492
expected_str.push_str(" virtio_mmio.device=4G@0x10000:4:42");
466-
assert_eq!(cl.as_str(), &expected_str);
493+
assert_eq!(
494+
cl.as_cstring().unwrap(),
495+
CString::new(expected_str.as_bytes()).unwrap()
496+
);
467497
}
468498

469499
#[test]
@@ -484,11 +514,14 @@ mod tests {
484514

485515
let mut cl = Cmdline::new(100);
486516
assert!(cl.insert_multiple("foo", &["bar"]).is_ok());
487-
assert_eq!(cl.as_str(), "foo=bar");
517+
assert_eq!(cl.as_cstring().unwrap().as_bytes_with_nul(), b"foo=bar\0");
488518

489519
let mut cl = Cmdline::new(100);
490520
assert!(cl.insert_multiple("foo", &["bar", "baz"]).is_ok());
491-
assert_eq!(cl.as_str(), "foo=bar,baz");
521+
assert_eq!(
522+
cl.as_cstring().unwrap().as_bytes_with_nul(),
523+
b"foo=bar,baz\0"
524+
);
492525
}
493526

494527
#[test]

src/loader/mod.rs

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ pub enum Error {
5656
#[cfg(all(feature = "pe", target_arch = "aarch64"))]
5757
Pe(pe::Error),
5858

59+
/// Invalid command line.
60+
InvalidCommandLine,
5961
/// Failed writing command line to guest memory.
6062
CommandLineCopy,
6163
/// Command line overflowed guest memory.
@@ -81,6 +83,7 @@ impl fmt::Display for Error {
8183
#[cfg(all(feature = "pe", target_arch = "aarch64"))]
8284
Error::Pe(ref _e) => "failed to load PE kernel image",
8385

86+
Error::InvalidCommandLine => "invalid command line provided",
8487
Error::CommandLineCopy => "failed writing command line to guest memory",
8588
Error::CommandLineOverflow => "command line overflowed guest memory",
8689
Error::InvalidKernelStartAddress => "invalid kernel start address",
@@ -101,6 +104,7 @@ impl std::error::Error for Error {
101104
#[cfg(all(feature = "pe", target_arch = "aarch64"))]
102105
Error::Pe(ref e) => Some(e),
103106

107+
Error::InvalidCommandLine => None,
104108
Error::CommandLineCopy => None,
105109
Error::CommandLineOverflow => None,
106110
Error::InvalidKernelStartAddress => None,
@@ -211,20 +215,26 @@ pub fn load_cmdline<M: GuestMemory>(
211215
guest_addr: GuestAddress,
212216
cmdline: &Cmdline,
213217
) -> Result<()> {
214-
let len = cmdline.as_str().len();
215-
if len == 0 {
216-
return Ok(());
217-
}
218+
// We need a null terminated string because that's what the Linux
219+
// kernel expects when parsing the command line:
220+
// https://elixir.bootlin.com/linux/v5.10.139/source/kernel/params.c#L179
221+
let cmdline_string = cmdline
222+
.as_cstring()
223+
.map_err(|_| Error::InvalidCommandLine)?;
224+
225+
let cmdline_bytes = cmdline_string.as_bytes_with_nul();
218226

219227
let end = guest_addr
220-
.checked_add(len as u64 + 1)
221-
.ok_or(Error::CommandLineOverflow)?; // Extra for null termination.
228+
// Underflow not possible because the cmdline contains at least
229+
// a byte (null terminator)
230+
.checked_add((cmdline_bytes.len() - 1) as u64)
231+
.ok_or(Error::CommandLineOverflow)?;
222232
if end > guest_mem.last_addr() {
223233
return Err(Error::CommandLineOverflow);
224234
}
225235

226236
guest_mem
227-
.write_slice(cmdline.as_str().as_bytes(), guest_addr)
237+
.write_slice(cmdline_bytes, guest_addr)
228238
.map_err(|_| Error::CommandLineCopy)?;
229239

230240
Ok(())
@@ -245,22 +255,44 @@ mod tests {
245255
#[test]
246256
fn test_cmdline_overflow() {
247257
let gm = create_guest_mem();
248-
let cmdline_address = GuestAddress(MEM_SIZE - 5);
249258
let mut cl = Cmdline::new(10);
250259
cl.insert_str("12345").unwrap();
260+
261+
let cmdline_address = GuestAddress(u64::MAX - 5);
262+
assert_eq!(
263+
Err(Error::CommandLineOverflow),
264+
load_cmdline(&gm, cmdline_address, &cl)
265+
);
266+
267+
let cmdline_address = GuestAddress(MEM_SIZE - 5);
251268
assert_eq!(
252269
Err(Error::CommandLineOverflow),
253270
load_cmdline(&gm, cmdline_address, &cl)
254271
);
272+
let cmdline_address = GuestAddress(MEM_SIZE - 6);
273+
assert!(load_cmdline(&gm, cmdline_address, &cl).is_ok());
255274
}
256275

257276
#[test]
258-
fn test_cmdline_write_end() {
277+
fn test_cmdline_write_end_regresion() {
259278
let gm = create_guest_mem();
260279
let mut cmdline_address = GuestAddress(45);
280+
let sample_buf = &[1; 100];
281+
282+
// Fill in guest memory with non zero bytes
283+
gm.write(sample_buf, cmdline_address).unwrap();
284+
261285
let mut cl = Cmdline::new(10);
262-
cl.insert_str("1234").unwrap();
263-
assert_eq!(Ok(()), load_cmdline(&gm, cmdline_address, &cl));
286+
287+
// Test loading an empty cmdline
288+
load_cmdline(&gm, cmdline_address, &cl).unwrap();
289+
let val: u8 = gm.read_obj(cmdline_address).unwrap();
290+
assert_eq!(val, b'\0');
291+
292+
// Test loading an non-empty cmdline
293+
cl.insert_str("123").unwrap();
294+
load_cmdline(&gm, cmdline_address, &cl).unwrap();
295+
264296
let val: u8 = gm.read_obj(cmdline_address).unwrap();
265297
assert_eq!(val, b'1');
266298
cmdline_address = cmdline_address.unchecked_add(1);
@@ -271,9 +303,6 @@ mod tests {
271303
assert_eq!(val, b'3');
272304
cmdline_address = cmdline_address.unchecked_add(1);
273305
let val: u8 = gm.read_obj(cmdline_address).unwrap();
274-
assert_eq!(val, b'4');
275-
cmdline_address = cmdline_address.unchecked_add(1);
276-
let val: u8 = gm.read_obj(cmdline_address).unwrap();
277306
assert_eq!(val, b'\0');
278307
}
279308
}

0 commit comments

Comments
 (0)