Skip to main content

krun_kernel/cmdline/
mod.rs

1// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3//
4// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
5// Use of this source code is governed by a BSD-style license that can be
6// found in the THIRD-PARTY file.
7
8//! Helper for creating valid kernel command line strings.
9
10use std::ffi::CString;
11use std::fmt;
12use std::result;
13
14/// The error type for command line building operations.
15#[derive(Eq, PartialEq, Debug)]
16pub enum Error {
17    /// Failed to copy to guest memory.
18    CommandLineCopy,
19    /// Command line string overflows guest memory.
20    CommandLineOverflow,
21    /// Operation would have resulted in a non-printable ASCII character.
22    InvalidAscii,
23    /// Key/Value Operation would have had a space in it.
24    HasSpace,
25    /// Key/Value Operation would have had an equals sign in it.
26    HasEquals,
27    /// Operation would have made the command line too large.
28    TooLarge,
29}
30
31impl fmt::Display for Error {
32    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
33        write!(
34            f,
35            "{}",
36            match *self {
37                Error::CommandLineCopy => "Failed to copy the command line string to guest memory",
38                Error::CommandLineOverflow => "Command line string overflows guest memory",
39                Error::InvalidAscii => "Command line string contains non-printable ASCII character",
40                Error::HasSpace => "Command line string contains a space",
41                Error::HasEquals => "Command line string contains an equals sign",
42                Error::TooLarge => "Command line inserting string would make command line too long",
43            }
44        )
45    }
46}
47
48/// Specialized Result type for command line operations.
49pub type Result<T> = result::Result<T, Error>;
50
51fn valid_char(c: char) -> bool {
52    matches!(c, ' '..='~')
53}
54
55fn valid_str(s: &str) -> Result<()> {
56    if s.chars().all(valid_char) {
57        Ok(())
58    } else {
59        Err(Error::InvalidAscii)
60    }
61}
62
63fn valid_element(s: &str) -> Result<()> {
64    if !s.chars().all(valid_char) {
65        Err(Error::InvalidAscii)
66    } else if s.contains(' ') {
67        Err(Error::HasSpace)
68    } else if s.contains('=') {
69        Err(Error::HasEquals)
70    } else {
71        Ok(())
72    }
73}
74
75/// A builder for a kernel command line string that validates the string as its being built. A
76/// `CString` can be constructed from this directly using `CString::new`.
77#[derive(Clone, Debug)]
78pub struct Cmdline {
79    line: String,
80    capacity: usize,
81}
82
83impl Cmdline {
84    /// Constructs an empty Cmdline with the given capacity, which includes the nul terminator.
85    /// Capacity must be greater than 0.
86    pub fn new(capacity: usize) -> Cmdline {
87        assert_ne!(capacity, 0);
88        Cmdline {
89            line: String::with_capacity(capacity),
90            capacity,
91        }
92    }
93
94    fn has_capacity(&self, more: usize) -> Result<()> {
95        let needs_space = usize::from(!self.line.is_empty());
96        if self.line.len() + more + needs_space < self.capacity {
97            Ok(())
98        } else {
99            Err(Error::TooLarge)
100        }
101    }
102
103    fn start_push(&mut self) {
104        if !self.line.is_empty() {
105            self.line.push(' ');
106        }
107    }
108
109    fn end_push(&mut self) {
110        // This assert is always true because of the `has_capacity` check that each insert method
111        // uses.
112        assert!(self.line.len() < self.capacity);
113    }
114
115    /// Returns the length of the command line.
116    pub fn len(&self) -> usize {
117        self.line.len()
118    }
119
120    /// Returns whether the command line is empty.
121    pub fn is_empty(&self) -> bool {
122        self.len() == 0
123    }
124
125    /// Validates and inserts a key value pair into this command line.
126    pub fn insert<T: AsRef<str>>(&mut self, key: T, val: T) -> Result<()> {
127        let k = key.as_ref();
128        let v = val.as_ref();
129
130        valid_element(k)?;
131        valid_element(v)?;
132        self.has_capacity(k.len() + v.len() + 1)?;
133
134        self.start_push();
135        self.line.push_str(k);
136        self.line.push('=');
137        self.line.push_str(v);
138        self.end_push();
139
140        Ok(())
141    }
142
143    /// Validates and inserts a string to the end of the current command line.
144    pub fn insert_str<T: AsRef<str>>(&mut self, slug: T) -> Result<()> {
145        let s = slug.as_ref();
146        valid_str(s)?;
147
148        self.has_capacity(s.len())?;
149
150        self.start_push();
151        self.line.push_str(s);
152        self.end_push();
153
154        Ok(())
155    }
156
157    /// Returns the cmdline in progress without nul termination.
158    pub fn as_str(&self) -> &str {
159        self.line.as_str()
160    }
161
162    /// Returns the cmdline in progress as CString.
163    pub fn as_cstring(&self) -> Result<CString> {
164        CString::new(self.line.clone()).map_err(|_| Error::InvalidAscii)
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    #[test]
173    fn insert_hello_world() {
174        let mut cl = Cmdline::new(100);
175        assert_eq!(cl.as_str(), "");
176        assert!(cl.insert("hello", "world").is_ok());
177        assert_eq!(cl.as_str(), "hello=world");
178        assert_eq!(cl.len(), "hello=world".len());
179        assert!(!cl.is_empty());
180
181        // Test clone.
182        let cl2 = cl.clone();
183        assert_eq!(cl2.as_str(), cl.as_str());
184    }
185
186    #[test]
187    fn insert_multi() {
188        let mut cl = Cmdline::new(100);
189        assert!(cl.insert("hello", "world").is_ok());
190        assert!(cl.insert("foo", "bar").is_ok());
191        assert_eq!(cl.as_str(), "hello=world foo=bar");
192    }
193
194    #[test]
195    fn insert_space() {
196        let mut cl = Cmdline::new(100);
197        assert_eq!(cl.insert("a ", "b"), Err(Error::HasSpace));
198        assert_eq!(cl.insert("a", "b "), Err(Error::HasSpace));
199        assert_eq!(cl.insert("a ", "b "), Err(Error::HasSpace));
200        assert_eq!(cl.insert(" a", "b"), Err(Error::HasSpace));
201        assert_eq!(cl.as_str(), "");
202    }
203
204    #[test]
205    fn insert_equals() {
206        let mut cl = Cmdline::new(100);
207        assert_eq!(cl.insert("a=", "b"), Err(Error::HasEquals));
208        assert_eq!(cl.insert("a", "b="), Err(Error::HasEquals));
209        assert_eq!(cl.insert("a=", "b "), Err(Error::HasEquals));
210        assert_eq!(cl.insert("=a", "b"), Err(Error::HasEquals));
211        assert_eq!(cl.insert("a", "=b"), Err(Error::HasEquals));
212        assert_eq!(cl.as_str(), "");
213    }
214
215    #[test]
216    fn insert_emoji() {
217        assert_eq!(valid_str("💖"), Err(Error::InvalidAscii));
218
219        let mut cl = Cmdline::new(100);
220        assert_eq!(cl.insert("heart", "💖"), Err(Error::InvalidAscii));
221        assert_eq!(cl.insert("💖", "love"), Err(Error::InvalidAscii));
222        assert_eq!(cl.as_str(), "");
223    }
224
225    #[test]
226    fn insert_string() {
227        let mut cl = Cmdline::new(13);
228        assert_eq!(cl.as_str(), "");
229        assert!(cl.insert_str("noapic").is_ok());
230        assert_eq!(cl.as_str(), "noapic");
231        assert!(cl.insert_str("nopci").is_ok());
232        assert_eq!(cl.as_str(), "noapic nopci");
233        assert_eq!(cl.as_str(), cl.as_cstring().unwrap().to_str().unwrap());
234    }
235
236    #[test]
237    fn insert_too_large() {
238        let mut cl = Cmdline::new(4);
239        assert_eq!(cl.insert("hello", "world"), Err(Error::TooLarge));
240        assert_eq!(cl.insert("a", "world"), Err(Error::TooLarge));
241        assert_eq!(cl.insert("hello", "b"), Err(Error::TooLarge));
242        assert!(cl.insert("a", "b").is_ok());
243        assert_eq!(cl.insert("a", "b"), Err(Error::TooLarge));
244        assert_eq!(cl.insert_str("a"), Err(Error::TooLarge));
245        assert_eq!(cl.as_str(), "a=b");
246
247        let mut cl = Cmdline::new(10);
248        assert!(cl.insert("ab", "ba").is_ok()); // adds 5 length
249        assert_eq!(cl.insert("c", "da"), Err(Error::TooLarge)); // adds 5 (including space) length
250        assert!(cl.insert("c", "d").is_ok()); // adds 4 (including space) length
251    }
252
253    #[test]
254    fn display_errors() {
255        assert_eq!(
256            Error::CommandLineCopy.to_string().as_str(),
257            "Failed to copy the command line string to guest memory"
258        );
259        assert_eq!(
260            Error::CommandLineOverflow.to_string().as_str(),
261            "Command line string overflows guest memory"
262        );
263        assert_eq!(
264            Error::InvalidAscii.to_string().as_str(),
265            "Command line string contains non-printable ASCII character"
266        );
267        assert_eq!(
268            Error::HasSpace.to_string().as_str(),
269            "Command line string contains a space"
270        );
271        assert_eq!(
272            Error::HasEquals.to_string().as_str(),
273            "Command line string contains an equals sign"
274        );
275        assert_eq!(
276            Error::TooLarge.to_string().as_str(),
277            "Command line inserting string would make command line too long"
278        );
279    }
280}