Skip to content

Commit efe5b91

Browse files
ioanachircadianpopa
authored andcommitted
micro_http: check arithmetic operations
Signed-off-by: Ioana Chirca <[email protected]>
1 parent 7a86af0 commit efe5b91

File tree

6 files changed

+135
-35
lines changed

6 files changed

+135
-35
lines changed

src/micro_http/src/common/mod.rs

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ pub enum RequestError {
2828
InvalidHeader,
2929
/// The Request is invalid and cannot be served.
3030
InvalidRequest,
31+
/// Overflow occurred when parsing a request.
32+
Overflow,
33+
/// Underflow occurred when parsing a request.
34+
Underflow,
3135
}
3236

3337
impl Display for RequestError {
@@ -39,6 +43,8 @@ impl Display for RequestError {
3943
Self::UnsupportedHeader => write!(f, "Unsupported header."),
4044
Self::InvalidHeader => write!(f, "Invalid header."),
4145
Self::InvalidRequest => write!(f, "Invalid request."),
46+
Self::Overflow => write!(f, "Overflow occurred when parsing a request."),
47+
Self::Underflow => write!(f, "Underflow occurred when parsing a request."),
4248
}
4349
}
4450
}
@@ -76,6 +82,10 @@ pub enum ServerError {
7682
ConnectionError(ConnectionError),
7783
/// Server maximum capacity has been reached.
7884
ServerFull,
85+
/// Overflow occured while processing messages.
86+
Overflow,
87+
/// Underflow occured while processing mesagges.
88+
Underflow,
7989
}
8090

8191
impl Display for ServerError {
@@ -84,6 +94,8 @@ impl Display for ServerError {
8494
Self::IOError(inner) => write!(f, "IO error: {}", inner),
8595
Self::ConnectionError(inner) => write!(f, "Connection error: {}", inner),
8696
Self::ServerFull => write!(f, "Server is full."),
97+
Self::Overflow => write!(f, "Overflow occured while processing messages."),
98+
Self::Underflow => write!(f, "Underflow occured while processing messages."),
8799
}
88100
}
89101
}
@@ -226,9 +238,11 @@ mod tests {
226238
fn eq(&self, other: &Self) -> bool {
227239
use self::ConnectionError::*;
228240
match (self, other) {
229-
(ParseError(_), ParseError(_)) => true,
241+
(ParseError(ref e), ParseError(ref other_e)) => e.eq(other_e),
230242
(ConnectionClosed, ConnectionClosed) => true,
231-
(StreamError(_), StreamError(_)) => true,
243+
(StreamError(ref e), StreamError(ref other_e)) => {
244+
format!("{}", e).eq(&format!("{}", other_e))
245+
}
232246
(InvalidWrite, InvalidWrite) => true,
233247
_ => false,
234248
}
@@ -284,30 +298,38 @@ mod tests {
284298

285299
#[test]
286300
fn test_display_request_error() {
301+
assert_eq!(
302+
format!("{}", RequestError::InvalidHeader),
303+
"Invalid header."
304+
);
287305
assert_eq!(
288306
format!("{}", RequestError::InvalidHttpMethod("test")),
289307
"Invalid HTTP Method: test"
290308
);
309+
assert_eq!(
310+
format!("{}", RequestError::InvalidHttpVersion("test")),
311+
"Invalid HTTP Version: test"
312+
);
313+
assert_eq!(
314+
format!("{}", RequestError::InvalidRequest),
315+
"Invalid request."
316+
);
291317
assert_eq!(
292318
format!("{}", RequestError::InvalidUri("test")),
293319
"Invalid URI: test"
294320
);
295321
assert_eq!(
296-
format!("{}", RequestError::InvalidHttpVersion("test")),
297-
"Invalid HTTP Version: test"
322+
format!("{}", RequestError::Overflow),
323+
"Overflow occurred when parsing a request."
298324
);
299325
assert_eq!(
300-
format!("{}", RequestError::InvalidHeader),
301-
"Invalid header."
326+
format!("{}", RequestError::Underflow),
327+
"Underflow occurred when parsing a request."
302328
);
303329
assert_eq!(
304330
format!("{}", RequestError::UnsupportedHeader),
305331
"Unsupported header."
306332
);
307-
assert_eq!(
308-
format!("{}", RequestError::InvalidRequest),
309-
"Invalid request."
310-
);
311333
}
312334

313335
#[test]
@@ -353,5 +375,13 @@ mod tests {
353375
),
354376
"IO error: Resource temporarily unavailable (os error 11)"
355377
);
378+
assert_eq!(
379+
format!("{}", ServerError::Overflow),
380+
"Overflow occured while processing messages."
381+
);
382+
assert_eq!(
383+
format!("{}", ServerError::Underflow),
384+
"Underflow occured while processing messages."
385+
);
356386
}
357387
}

src/micro_http/src/request.rs

Lines changed: 71 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ pub use common::RequestError;
88
use common::{Body, Method, Version};
99
use headers::Headers;
1010

11+
// This type represents the RequestLine raw parts: method, uri and version.
12+
type RequestLineParts<'a> = (&'a [u8], &'a [u8], &'a [u8]);
13+
1114
/// Finds the first occurence of `sequence` in the `bytes` slice.
1215
///
1316
/// Returns the starting position of the `sequence` in `bytes` or `None` if the
@@ -57,13 +60,15 @@ impl Uri {
5760
const HTTP_SCHEME_PREFIX: &str = "http://";
5861

5962
if self.string.starts_with(HTTP_SCHEME_PREFIX) {
63+
// Slice access is safe because we checked above that `self.string` size <= `HTTP_SCHEME_PREFIX.len()`.
6064
let without_scheme = &self.string[HTTP_SCHEME_PREFIX.len()..];
6165
if without_scheme.is_empty() {
6266
return "";
6367
}
6468
// The host in this case includes the port and contains the bytes after http:// up to
6569
// the next '/'.
6670
match without_scheme.bytes().position(|byte| byte == b'/') {
71+
// Slice access is safe because `position` validates that `len` is a valid index.
6772
Some(len) => &without_scheme[len..],
6873
None => "",
6974
}
@@ -86,24 +91,36 @@ pub struct RequestLine {
8691
}
8792

8893
impl RequestLine {
89-
fn parse_request_line(request_line: &[u8]) -> (&[u8], &[u8], &[u8]) {
94+
fn parse_request_line(
95+
request_line: &[u8],
96+
) -> std::result::Result<RequestLineParts, RequestError> {
9097
if let Some(method_end) = find(request_line, &[SP]) {
98+
// The slice access is safe because `find` validates that `method_end` < `request_line` size.
9199
let method = &request_line[..method_end];
92100

93-
let uri_and_version = &request_line[(method_end + 1)..];
101+
// `uri_start` <= `request_line` size.
102+
let uri_start = method_end.checked_add(1).ok_or(RequestError::Overflow)?;
103+
104+
// Slice access is safe because `uri_start` <= `request_line` size.
105+
// If `uri_start` == `request_line` size, then `uri_and_version` will be an empty slice.
106+
let uri_and_version = &request_line[uri_start..];
94107

95108
if let Some(uri_end) = find(uri_and_version, &[SP]) {
109+
// Slice access is safe because `find` validates that `uri_end` < `uri_and_version` size.
96110
let uri = &uri_and_version[..uri_end];
97111

98-
let version = &uri_and_version[(uri_end + 1)..];
112+
// `version_start` <= `uri_and_version` size.
113+
let version_start = uri_end.checked_add(1).ok_or(RequestError::Overflow)?;
99114

100-
return (method, uri, version);
101-
}
115+
// Slice access is safe because `version_start` <= `uri_and_version` size.
116+
let version = &uri_and_version[version_start..];
102117

103-
return (method, uri_and_version, b"");
118+
return Ok((method, uri, version));
119+
}
104120
}
105121

106-
(b"", b"", b"")
122+
// Request Line can be valid only if it contains the method, uri and version separated with SP.
123+
Err(RequestError::InvalidRequest)
107124
}
108125

109126
/// Tries to parse a byte stream in a request line. Fails if the request line is malformed.
@@ -113,7 +130,7 @@ impl RequestLine {
113130
/// `InvalidHttpVersion` is returned if the specified HTTP version is unsupported.
114131
/// `InvalidUri` is returned if the specified Uri is not valid.
115132
pub fn try_from(request_line: &[u8]) -> Result<Self, RequestError> {
116-
let (method, uri, version) = Self::parse_request_line(request_line);
133+
let (method, uri, version) = Self::parse_request_line(request_line)?;
117134

118135
Ok(Self {
119136
method: Method::try_from(method)?,
@@ -123,9 +140,10 @@ impl RequestLine {
123140
}
124141

125142
// Returns the minimum length of a valid request. The request must contain
126-
// the method (GET), the URI (minmum 1 character), the HTTP version(HTTP/DIGIT.DIGIT) and
143+
// the method (GET), the URI (minimum 1 character), the HTTP version(HTTP/DIGIT.DIGIT) and
127144
// 2 separators (SP).
128145
fn min_len() -> usize {
146+
// Addition is safe because these are small constants.
129147
Method::Get.raw().len() + 1 + Version::Http10.raw().len() + 2
130148
}
131149
}
@@ -148,8 +166,9 @@ impl Request {
148166
/// The byte slice is expected to have the following format: </br>
149167
/// * Request Line: "GET SP Request-uri SP HTTP/1.0 CRLF" - Mandatory </br>
150168
/// * Request Headers "<headers> CRLF"- Optional </br>
169+
/// * Empty Line "CRLF" </br>
151170
/// * Entity Body - Optional </br>
152-
/// The request headers and the entity body is not parsed and None is returned because
171+
/// The request headers and the entity body are not parsed and None is returned because
153172
/// these are not used by the MMDS server.
154173
/// The only supported method is GET and the HTTP protocol is expected to be HTTP/1.0
155174
/// or HTTP/1.1.
@@ -163,7 +182,7 @@ impl Request {
163182
/// extern crate micro_http;
164183
/// use micro_http::Request;
165184
///
166-
/// let http_request = Request::try_from(b"GET http://localhost/home HTTP/1.0\r\n");
185+
/// let http_request = Request::try_from(b"GET http://localhost/home HTTP/1.0\r\n\r\n").unwrap();
167186
/// ```
168187
pub fn try_from(byte_stream: &[u8]) -> Result<Self, RequestError> {
169188
// The first line of the request is the Request Line. The line ending is CR LF.
@@ -173,6 +192,7 @@ impl Request {
173192
None => return Err(RequestError::InvalidRequest),
174193
};
175194

195+
// Slice access is safe because `find` validates that `request_line_end` < `byte_stream` size.
176196
let request_line_bytes = &byte_stream[..request_line_end];
177197
if request_line_bytes.len() < RequestLine::min_len() {
178198
return Err(RequestError::InvalidRequest);
@@ -193,8 +213,20 @@ impl Request {
193213
Some(headers_end) => {
194214
// Parse the request headers.
195215
// Start by removing the leading CR LF from them.
196-
let headers_and_body = &byte_stream[(request_line_end + CRLF_LEN)..];
216+
let headers_start = request_line_end
217+
.checked_add(CRLF_LEN)
218+
.ok_or(RequestError::Overflow)?;
219+
// Slice access is safe because starting from `request_line_end` there are at least two CRLF
220+
// (enforced by `find` at the start of this method).
221+
let headers_and_body = &byte_stream[headers_start..];
222+
// Because we advanced the start with CRLF_LEN, we now have to subtract CRLF_LEN
223+
// from the end in order to keep the same window.
224+
// Underflow is not possible here because `byte_stream[request_line_end..]` starts with CR LF,
225+
// so `headers_end` can be either zero (this case is treated separately in the first match arm)
226+
// or >= 3 (current case).
197227
let headers_end = headers_end - CRLF_LEN;
228+
// Slice access is safe because `headers_end` is checked above
229+
// (`find` gives a valid position, and subtracting 2 can't underflow).
198230
let headers = Headers::try_from(&headers_and_body[..headers_end])?;
199231

200232
// Parse the body of the request.
@@ -205,16 +237,22 @@ impl Request {
205237
None
206238
}
207239
content_length => {
240+
// Multiplication is safe because `CRLF_LEN` is a small constant.
241+
let crlf_end = headers_end
242+
.checked_add(2 * CRLF_LEN)
243+
.ok_or(RequestError::Overflow)?;
244+
// This can't underflow because `headers_and_body.len()` >= `crlf_end`.
245+
let body_len = headers_and_body.len() - crlf_end;
208246
// Headers suggest we have a body, but the buffer is shorter than the specified
209247
// content length.
210-
if headers_and_body.len() - (headers_end + 2 * CRLF_LEN)
211-
< content_length as usize
212-
{
248+
if body_len < content_length as usize {
213249
return Err(RequestError::InvalidRequest);
214250
}
215-
let body_as_bytes = &headers_and_body[(headers_end + 2 * CRLF_LEN)..];
251+
// Slice access is safe because `crlf_end` is the index after two CRLF
252+
// (it is <= `headers_and_body` size).
253+
let body_as_bytes = &headers_and_body[crlf_end..];
216254
// If the actual length of the body is different than the `Content-Length` value
217-
// in the headers then this request is invalid.
255+
// in the headers, then this request is invalid.
218256
if body_as_bytes.len() == content_length as usize {
219257
Some(Body::new(body_as_bytes))
220258
} else {
@@ -343,6 +381,13 @@ mod tests {
343381
expected_request_line
344382
);
345383

384+
// Test for invalid request missing the separator.
385+
let request_line = b"GET";
386+
assert_eq!(
387+
RequestLine::try_from(request_line).unwrap_err(),
388+
RequestError::InvalidRequest
389+
);
390+
346391
// Test for invalid method.
347392
let request_line = b"POST http://localhost/home HTTP/1.0";
348393
assert_eq!(
@@ -368,14 +413,14 @@ mod tests {
368413
let request_line = b"nothing";
369414
assert_eq!(
370415
RequestLine::try_from(request_line).unwrap_err(),
371-
RequestError::InvalidHttpMethod("Unsupported HTTP method.")
416+
RequestError::InvalidRequest
372417
);
373418

374419
// Test for invalid format with no version.
375420
let request_line = b"GET /";
376421
assert_eq!(
377422
RequestLine::try_from(request_line).unwrap_err(),
378-
RequestError::InvalidHttpVersion("Unsupported HTTP version.")
423+
RequestError::InvalidRequest
379424
);
380425
}
381426

@@ -398,6 +443,13 @@ mod tests {
398443
assert_eq!(request.http_version(), Version::Http10);
399444
assert!(request.body.is_none());
400445

446+
// Test for invalid Request (missing CR LF).
447+
let request_bytes = b"GET / HTTP/1.1";
448+
assert_eq!(
449+
Request::try_from(request_bytes).unwrap_err(),
450+
RequestError::InvalidRequest
451+
);
452+
401453
// Test for invalid Request (length is less than minimum).
402454
let request_bytes = b"GET";
403455
assert_eq!(
@@ -442,7 +494,6 @@ mod tests {
442494
// Test for an invalid content length.
443495
let request = Request::try_from(
444496
b"PATCH http://localhost/home HTTP/1.1\r\n\
445-
Expect: 100-continue\r\n\
446497
Content-Length: 5000\r\n\r\nthis is a short body",
447498
)
448499
.unwrap_err();

src/micro_http/src/response.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ impl ResponseHeaders {
105105
let delimitator = b", ";
106106
for (idx, method) in self.allow.iter().enumerate() {
107107
buf.write_all(method.raw())?;
108+
// We check above that `self.allow` is not empty.
108109
if idx < self.allow.len() - 1 {
109110
buf.write_all(delimitator)?;
110111
}

src/micro_http/src/server.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,10 @@ impl<T: Read + Write> ClientConnection<T> {
149149
}
150150
}
151151
}
152-
self.in_flight_response_count += parsed_requests.len() as u32;
152+
self.in_flight_response_count = self
153+
.in_flight_response_count
154+
.checked_add(parsed_requests.len() as u32)
155+
.ok_or(ServerError::Overflow)?;
153156
// If the state of the connection has changed, we need to update
154157
// the event set in the `epoll` structure.
155158
if self.connection.pending_write() {
@@ -181,11 +184,15 @@ impl<T: Read + Write> ClientConnection<T> {
181184
Ok(())
182185
}
183186

184-
fn enqueue_response(&mut self, response: Response) {
187+
fn enqueue_response(&mut self, response: Response) -> Result<()> {
185188
if self.state != ClientConnectionState::Closed {
186189
self.connection.enqueue_response(response);
187190
}
188-
self.in_flight_response_count -= 1;
191+
self.in_flight_response_count = self
192+
.in_flight_response_count
193+
.checked_sub(1)
194+
.ok_or(ServerError::Underflow)?;
195+
Ok(())
189196
}
190197

191198
// Returns `true` if the connection is closed and safe to drop.
@@ -453,6 +460,7 @@ impl HttpServer {
453460
///
454461
/// # Errors
455462
/// `IOError` is returned when an `epoll::ctl` operation fails.
463+
/// `Underflow` is returned when `enqueue_response` fails.
456464
pub fn respond(&mut self, response: ServerResponse) -> Result<()> {
457465
if let Some(client_connection) = self.connections.get_mut(&(response.id as i32)) {
458466
// If the connection was incoming before we enqueue the response, we change its
@@ -461,7 +469,7 @@ impl HttpServer {
461469
client_connection.state = ClientConnectionState::AwaitingOutgoing;
462470
Self::epoll_mod(&self.epoll, response.id as RawFd, epoll::EventSet::OUT)?;
463471
}
464-
client_connection.enqueue_response(response.response);
472+
client_connection.enqueue_response(response.response)?;
465473
}
466474
Ok(())
467475
}

0 commit comments

Comments
 (0)