Skip to content

Commit 7a86af0

Browse files
Alexandra Iordachedianpopa
authored andcommitted
virtio net: check vnet header in tx frames
This commit removes a potential panic condition caused by the virtio net device expecting to find a vnet header in every frame. In case a malformed shorter frame shows up on the TX path (large enough buffers make sure that it can't happen on RX), error messages are now logged and a dedicated metric is emitted. With this occasion, several unused device error variants have been removed. Signed-off-by: Alexandra Iordache <[email protected]>
1 parent 385afca commit 7a86af0

File tree

5 files changed

+98
-44
lines changed

5 files changed

+98
-44
lines changed

CHANGELOG.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,17 @@
1818
requests.
1919
- Added metrics for the vsock device.
2020
- Added devtool strip command which removes debug symbols from the release
21-
binaries.
22-
- Any number of whitespace characters are accepted after ":" when parsing HTTP
23-
headers.
21+
- Added the `tx_malformed_frames` metric for the virtio net device, emitted
22+
when a TX frame missing the VNET header is encountered.
2423

2524
### Fixed
2625

2726
- Added `--version` flag to both Firecracker and Jailer.
2827
- Return `405 Method Not Allowed` MMDS response for non HTTP `GET` MMDS
2928
requests originating from guest.
3029
- Fixed folder permissions in the jail (#1802).
30+
- Any number of whitespace characters are accepted after ":" when parsing HTTP
31+
headers.
3132

3233
### Changed
3334
- Updated CVE-2019-3016 mitigation information in

src/devices/src/lib.rs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ extern crate versionize;
2020
extern crate versionize_derive;
2121
extern crate vm_memory;
2222

23-
use rate_limiter::Error as RateLimiterError;
2423
use std::io;
2524

2625
mod bus;
@@ -39,15 +38,12 @@ pub(crate) fn report_net_event_fail(err: Error) {
3938

4039
#[derive(Debug)]
4140
pub enum Error {
42-
FailedReadingQueue {
43-
event_type: &'static str,
44-
underlying: io::Error,
45-
},
41+
/// Failed to read from the TAP device.
4642
FailedReadTap,
43+
/// Failed to signal the virtio used queue.
4744
FailedSignalingUsedQueue(io::Error),
48-
RateLimited(RateLimiterError),
49-
PayloadExpected,
45+
/// IO error.
5046
IoError(io::Error),
51-
NoAvailBuffers,
52-
SpuriousEvent,
47+
/// Device received malformed payload.
48+
MalformedPayload,
5349
}

src/devices/src/virtio/net/device.rs

Lines changed: 82 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@ use dumbo::{EthernetFrame, MacAddr, MAC_ADDR_LEN};
1717
use libc::EAGAIN;
1818
use logger::{Metric, METRICS};
1919
use rate_limiter::{BucketUpdate, RateLimiter, TokenType};
20-
#[cfg(not(test))]
21-
use std::io::Read;
20+
2221
use std::io::Write;
22+
#[cfg(not(test))]
23+
use std::io::{self, Read};
2324
use std::sync::atomic::AtomicUsize;
2425
use std::sync::atomic::Ordering;
2526
use std::sync::Arc;
26-
use std::{cmp, io, mem, result};
27+
use std::{cmp, mem, result};
2728
use utils::eventfd::EventFd;
2829
use utils::net::Tap;
2930
use virtio_gen::virtio_net::{
@@ -39,12 +40,20 @@ fn vnet_hdr_len() -> usize {
3940

4041
// Frames being sent/received through the network device model have a VNET header. This
4142
// function returns a slice which holds the L2 frame bytes without this header.
42-
fn frame_bytes_from_buf(buf: &[u8]) -> &[u8] {
43-
&buf[vnet_hdr_len()..]
43+
fn frame_bytes_from_buf(buf: &[u8]) -> Result<&[u8]> {
44+
if buf.len() < vnet_hdr_len() {
45+
Err(Error::VnetHeaderMissing)
46+
} else {
47+
Ok(&buf[vnet_hdr_len()..])
48+
}
4449
}
4550

46-
fn frame_bytes_from_buf_mut(buf: &mut [u8]) -> &mut [u8] {
47-
&mut buf[vnet_hdr_len()..]
51+
fn frame_bytes_from_buf_mut(buf: &mut [u8]) -> Result<&mut [u8]> {
52+
if buf.len() < vnet_hdr_len() {
53+
Err(Error::VnetHeaderMissing)
54+
} else {
55+
Ok(&mut buf[vnet_hdr_len()..])
56+
}
4857
}
4958

5059
// This initializes to all 0 the VNET hdr part of a buf.
@@ -332,34 +341,40 @@ impl Net {
332341
frame_buf: &[u8],
333342
tap: &mut Tap,
334343
guest_mac: Option<MacAddr>,
335-
) -> bool {
344+
) -> Result<bool> {
345+
let checked_frame = |frame_buf| {
346+
frame_bytes_from_buf(frame_buf).map_err(|e| {
347+
error!("VNET header missing in the TX frame.");
348+
METRICS.net.tx_malformed_frames.inc();
349+
e
350+
})
351+
};
336352
if let Some(ns) = mmds_ns {
337-
if ns.detour_frame(frame_bytes_from_buf(frame_buf)) {
353+
if ns.detour_frame(checked_frame(frame_buf)?) {
338354
METRICS.mmds.rx_accepted.inc();
339355

340356
// MMDS frames are not accounted by the rate limiter.
341357
rate_limiter.manual_replenish(frame_buf.len() as u64, TokenType::Bytes);
342358
rate_limiter.manual_replenish(1, TokenType::Ops);
343359

344360
// MMDS consumed the frame.
345-
return true;
361+
return Ok(true);
346362
}
347363
}
348364

349365
// This frame goes to the TAP.
350366

351367
// Check for guest MAC spoofing.
352368
if let Some(mac) = guest_mac {
353-
let _ = EthernetFrame::from_bytes(&frame_buf[vnet_hdr_len()..]).and_then(|eth_frame| {
369+
let _ = EthernetFrame::from_bytes(checked_frame(frame_buf)?).and_then(|eth_frame| {
354370
if mac != eth_frame.src_mac() {
355371
METRICS.net.tx_spoofed_mac_count.inc();
356372
}
357373
Ok(())
358374
});
359375
}
360376

361-
let write_result = tap.write(frame_buf);
362-
match write_result {
377+
match tap.write(frame_buf) {
363378
Ok(_) => {
364379
METRICS.net.tx_bytes_count.add(frame_buf.len());
365380
METRICS.net.tx_packets_count.inc();
@@ -370,13 +385,14 @@ impl Net {
370385
METRICS.net.tx_fails.inc();
371386
}
372387
};
373-
false
388+
Ok(false)
374389
}
375390

376391
// We currently prioritize packets from the MMDS over regular network packets.
377-
fn read_from_mmds_or_tap(&mut self) -> io::Result<usize> {
392+
fn read_from_mmds_or_tap(&mut self) -> Result<usize> {
378393
if let Some(ns) = self.mmds_ns.as_mut() {
379-
if let Some(len) = ns.write_next_frame(frame_bytes_from_buf_mut(&mut self.rx_frame_buf))
394+
if let Some(len) =
395+
ns.write_next_frame(frame_bytes_from_buf_mut(&mut self.rx_frame_buf)?)
380396
{
381397
let len = len.get();
382398
METRICS.mmds.tx_frames.inc();
@@ -386,7 +402,7 @@ impl Net {
386402
}
387403
}
388404

389-
self.read_tap()
405+
self.read_tap().map_err(Error::IO)
390406
}
391407

392408
fn process_rx(&mut self) -> result::Result<(), DeviceError> {
@@ -401,7 +417,7 @@ impl Net {
401417
break;
402418
}
403419
}
404-
Err(e) => {
420+
Err(Error::IO(e)) => {
405421
// The tap device is non-blocking, so any error aside from EAGAIN is
406422
// unexpected.
407423
match e.raw_os_error() {
@@ -414,6 +430,9 @@ impl Net {
414430
};
415431
break;
416432
}
433+
Err(e) => {
434+
error!("Spurious error in network RX: {:?}", e);
435+
}
417436
}
418437
}
419438

@@ -523,14 +542,15 @@ impl Net {
523542
}
524543
}
525544

526-
if Self::write_to_mmds_or_tap(
545+
let frame_consumed_by_mmds = Self::write_to_mmds_or_tap(
527546
self.mmds_ns.as_mut(),
528547
&mut self.tx_rate_limiter,
529548
&self.tx_frame_buf[..read_count],
530549
&mut self.tap,
531550
self.guest_mac,
532-
) && !self.rx_deferred_frame
533-
{
551+
)
552+
.unwrap_or_else(|_| false);
553+
if frame_consumed_by_mmds && !self.rx_deferred_frame {
534554
// MMDS consumed this frame/request, let's also try to process the response.
535555
process_rx_for_mmds = true;
536556
}
@@ -893,6 +913,16 @@ pub(crate) mod tests {
893913

894914
#[test]
895915
fn test_vnet_helpers() {
916+
let mut frame_buf = vec![42u8; vnet_hdr_len() - 1];
917+
assert_eq!(
918+
format!("{:?}", frame_bytes_from_buf(&frame_buf)),
919+
"Err(VnetHeaderMissing)"
920+
);
921+
assert_eq!(
922+
format!("{:?}", frame_bytes_from_buf_mut(&mut frame_buf)),
923+
"Err(VnetHeaderMissing)"
924+
);
925+
896926
let mut frame_buf: [u8; MAX_BUFFER_SIZE] = [42u8; MAX_BUFFER_SIZE];
897927

898928
let vnet_hdr_len_ = mem::size_of::<virtio_net_hdr_v1>();
@@ -903,10 +933,10 @@ pub(crate) mod tests {
903933
assert_eq!(zero_vnet_hdr, &frame_buf[..vnet_hdr_len_]);
904934

905935
let payload = vec![42u8; MAX_BUFFER_SIZE - vnet_hdr_len_];
906-
assert_eq!(payload, frame_bytes_from_buf(&frame_buf));
936+
assert_eq!(payload, frame_bytes_from_buf(&frame_buf).unwrap());
907937

908938
{
909-
let payload = frame_bytes_from_buf_mut(&mut frame_buf);
939+
let payload = frame_bytes_from_buf_mut(&mut frame_buf).unwrap();
910940
payload[0] = 15;
911941
}
912942
assert_eq!(frame_buf[vnet_hdr_len_], 15);
@@ -996,6 +1026,7 @@ pub(crate) mod tests {
9961026
}
9971027

9981028
#[test]
1029+
#[allow(clippy::cognitive_complexity)]
9991030
fn test_event_processing() {
10001031
let mut event_manager = EventManager::new().unwrap();
10011032
let mut net = Net::default_net(TestMutators::default());
@@ -1050,20 +1081,39 @@ pub(crate) mod tests {
10501081
net.rx_bytes_read = 0;
10511082
}
10521083

1053-
// Now let's move on to the actual device events.
10541084
{
1055-
// testing TX_QUEUE_EVENT
1085+
// Send an invalid frame (too small, VNET header missing).
10561086
txq.avail.idx.set(1);
10571087
txq.avail.ring[0].set(0);
1058-
txq.dtable[0].set(daddr, 0x1000, 0, 0);
1088+
txq.dtable[0].set(daddr, 1, 0, 0);
10591089

1090+
// Trigger the TX handler.
10601091
net.queue_evts[TX_INDEX].write(1).unwrap();
10611092
let event = EpollEvent::new(EventSet::IN, net.queue_evts[TX_INDEX].as_raw_fd() as u64);
1062-
net.process(&event, &mut event_manager);
1093+
check_metric_after_block!(
1094+
&METRICS.net.tx_malformed_frames,
1095+
1,
1096+
net.process(&event, &mut event_manager)
1097+
);
1098+
10631099
// Make sure the data queue advanced.
10641100
assert_eq!(txq.used.idx.get(), 1);
10651101
}
10661102

1103+
// Now let's move on to the actual device events.
1104+
{
1105+
// testing TX_QUEUE_EVENT
1106+
txq.avail.idx.set(2);
1107+
txq.avail.ring[1].set(1);
1108+
txq.dtable[1].set(daddr, 0x1000, 0, 0);
1109+
1110+
net.queue_evts[TX_INDEX].write(1).unwrap();
1111+
let event = EpollEvent::new(EventSet::IN, net.queue_evts[TX_INDEX].as_raw_fd() as u64);
1112+
net.process(&event, &mut event_manager);
1113+
// Make sure the data queue advanced.
1114+
assert_eq!(txq.used.idx.get(), 2);
1115+
}
1116+
10671117
{
10681118
// testing RX_TAP_EVENT
10691119

@@ -1078,7 +1128,7 @@ pub(crate) mod tests {
10781128
let tap_event = EpollEvent::new(EventSet::IN, net.tap.as_raw_fd() as u64);
10791129
net.process(&tap_event, &mut event_manager);
10801130
assert!(net.rx_deferred_frame);
1081-
assert_eq!(net.interrupt_evt.read().unwrap(), 3);
1131+
assert_eq!(net.interrupt_evt.read().unwrap(), 4);
10821132
// The #cfg(test) enabled version of read_tap always returns 1234 bytes (or the len of
10831133
// the buffer, whichever is smaller).
10841134
assert_eq!(rxq.used.ring[0].get().len, 1234);
@@ -1162,7 +1212,7 @@ pub(crate) mod tests {
11621212
{
11631213
// Create an ethernet frame.
11641214
let eth_frame_i = EthernetFrame::write_incomplete(
1165-
frame_bytes_from_buf_mut(&mut net.tx_frame_buf),
1215+
frame_bytes_from_buf_mut(&mut net.tx_frame_buf).unwrap(),
11661216
tha,
11671217
sha,
11681218
ETHERTYPE_ARP,
@@ -1198,7 +1248,8 @@ pub(crate) mod tests {
11981248
&net.tx_frame_buf[..packet_len],
11991249
&mut net.tap,
12001250
Some(sha),
1201-
))
1251+
)
1252+
.unwrap())
12021253
);
12031254

12041255
// Validate that MMDS has a response and we can retrieve it.
@@ -1223,7 +1274,7 @@ pub(crate) mod tests {
12231274
{
12241275
// Create an ethernet frame.
12251276
let eth_frame_i = EthernetFrame::write_incomplete(
1226-
frame_bytes_from_buf_mut(&mut net.tx_frame_buf),
1277+
frame_bytes_from_buf_mut(&mut net.tx_frame_buf).unwrap(),
12271278
dst_mac,
12281279
guest_mac,
12291280
ETHERTYPE_ARP,

src/devices/src/virtio/net/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,12 @@ pub enum Error {
3030
TapSetVnetHdrSize(TapError),
3131
/// Enabling tap interface failed.
3232
TapEnable(TapError),
33-
/// EventFd
33+
/// EventFd error.
3434
EventFd(io::Error),
35+
/// IO error.
36+
IO(io::Error),
37+
/// The VNET header is missing from the frame.
38+
VnetHeaderMissing,
3539
}
3640

3741
pub type Result<T> = result::Result<T, Error>;

src/logger/src/metrics.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,8 @@ pub struct NetDeviceMetrics {
440440
pub rx_count: SharedMetric,
441441
/// Number of transmitted bytes.
442442
pub tx_bytes_count: SharedMetric,
443+
/// Number of malformed TX frames.
444+
pub tx_malformed_frames: SharedMetric,
443445
/// Number of errors while transmitting data.
444446
pub tx_fails: SharedMetric,
445447
/// Number of successful write operations while transmitting data.

0 commit comments

Comments
 (0)