Skip to content

Commit aff1abb

Browse files
committed
Fix parsing of abstract Unix socket addresses
These start with a null byte, don't end with a null byte and can contain embedded null bytes.
1 parent f5f4ad7 commit aff1abb

File tree

3 files changed

+135
-19
lines changed

3 files changed

+135
-19
lines changed

src/unix/nlas.rs

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,79 @@
11
// SPDX-License-Identifier: MIT
22

3+
use std::{
4+
ffi::{CStr, OsString},
5+
os::unix::ffi::{OsStrExt, OsStringExt},
6+
};
7+
38
use anyhow::Context;
49
use byteorder::{ByteOrder, NativeEndian};
510
use netlink_packet_utils::{
611
buffer,
712
nla::{self, DefaultNla, NlaBuffer},
8-
parsers::{parse_string, parse_u32, parse_u8},
13+
parsers::{parse_u32, parse_u8},
914
traits::{Emitable, Parseable},
1015
DecodeError,
1116
};
1217

1318
use crate::constants::*;
1419

20+
#[derive(Debug, Eq, PartialEq, Clone)]
21+
pub enum UnixDiagName {
22+
/// Filesystem pathname to which the socket was bound.
23+
Pathname(OsString),
24+
/// Abstract socket address to which the socket was bound.
25+
Abstract(Vec<u8>),
26+
}
27+
28+
impl<T: AsRef<[u8]>> Parseable<T> for UnixDiagName {
29+
fn parse(buf: &T) -> Result<Self, DecodeError> {
30+
let buf = buf.as_ref();
31+
if let Some((0, address)) = buf.split_first() {
32+
Ok(UnixDiagName::Abstract(address.to_owned()))
33+
} else {
34+
Ok(UnixDiagName::Pathname(OsString::from_vec(
35+
CStr::from_bytes_with_nul(buf)
36+
.context("pathname is not null-terminated")?
37+
.to_owned()
38+
.into(),
39+
)))
40+
}
41+
}
42+
}
43+
44+
impl Emitable for UnixDiagName {
45+
fn buffer_len(&self) -> usize {
46+
match self {
47+
UnixDiagName::Pathname(pathname) => pathname.len() + 1,
48+
UnixDiagName::Abstract(address) => address.len() + 1,
49+
}
50+
}
51+
52+
fn emit(&self, buffer: &mut [u8]) {
53+
match self {
54+
UnixDiagName::Pathname(pathname) => {
55+
let (last, first) = buffer
56+
.split_last_mut()
57+
.expect("buffer should not be empty");
58+
first.copy_from_slice(pathname.as_bytes());
59+
*last = 0;
60+
}
61+
UnixDiagName::Abstract(address) => {
62+
let (first, last) = buffer
63+
.split_first_mut()
64+
.expect("buffer should not be empty");
65+
*first = 0;
66+
last.copy_from_slice(address);
67+
}
68+
}
69+
}
70+
}
71+
1572
#[derive(Debug, Eq, PartialEq, Clone)]
1673
pub enum Nla {
17-
/// Path to which the socket was bound. This attribute is known as
74+
/// Name to which the socket was bound. This attribute is known as
1875
/// `UNIX_DIAG_NAME` in the kernel.
19-
Name(String),
76+
Name(UnixDiagName),
2077
/// VFS information for this socket. This attribute is known as
2178
/// `UNIX_DIAG_VFS` in the kernel.
2279
Vfs(Vfs),
@@ -246,8 +303,7 @@ impl nla::Nla for Nla {
246303
fn value_len(&self) -> usize {
247304
use self::Nla::*;
248305
match *self {
249-
// +1 because we need to append a null byte
250-
Name(ref s) => s.as_bytes().len() + 1,
306+
Name(ref s) => s.buffer_len(),
251307
Vfs(_) => VFS_LEN,
252308
Peer(_) => 4,
253309
PendingConnections(ref v) => 4 * v.len(),
@@ -261,10 +317,7 @@ impl nla::Nla for Nla {
261317
fn emit_value(&self, buffer: &mut [u8]) {
262318
use self::Nla::*;
263319
match *self {
264-
Name(ref s) => {
265-
buffer[..s.len()].copy_from_slice(s.as_bytes());
266-
buffer[s.len()] = 0;
267-
}
320+
Name(ref s) => s.emit(buffer),
268321
Vfs(ref value) => value.emit(buffer),
269322
Peer(value) => NativeEndian::write_u32(buffer, value),
270323
PendingConnections(ref values) => {
@@ -301,10 +354,10 @@ impl<'a, T: AsRef<[u8]> + ?Sized> Parseable<NlaBuffer<&'a T>> for Nla {
301354
fn parse(buf: &NlaBuffer<&'a T>) -> Result<Self, DecodeError> {
302355
let payload = buf.value();
303356
Ok(match buf.kind() {
304-
UNIX_DIAG_NAME => {
305-
let err = "invalid UNIX_DIAG_NAME value";
306-
Self::Name(parse_string(payload).context(err)?)
307-
}
357+
UNIX_DIAG_NAME => Self::Name(
358+
UnixDiagName::parse(&payload)
359+
.context("invalid UNIX_DIAG_NAME value")?,
360+
),
308361
UNIX_DIAG_VFS => {
309362
let err = "invalid UNIX_DIAG_VFS value";
310363
let buf = VfsBuffer::new_checked(payload).context(err)?;

src/unix/response.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use smallvec::SmallVec;
1313

1414
use crate::{
1515
constants::*,
16-
unix::nlas::{MemInfo, Nla},
16+
unix::nlas::{MemInfo, Nla, UnixDiagName},
1717
};
1818

1919
pub const UNIX_RESPONSE_HEADER_LEN: usize = 16;
@@ -92,7 +92,7 @@ impl UnixResponse {
9292
})
9393
}
9494

95-
pub fn name(&self) -> Option<&String> {
95+
pub fn name(&self) -> Option<&UnixDiagName> {
9696
self.nlas.iter().find_map(|nla| {
9797
if let Nla::Name(name) = nla {
9898
Some(name)

src/unix/tests.rs

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ use netlink_packet_utils::traits::{Emitable, Parseable};
55
use crate::{
66
constants::*,
77
unix::{
8-
nlas::Nla, ShowFlags, StateFlags, UnixRequest, UnixResponse,
9-
UnixResponseBuffer, UnixResponseHeader,
8+
nlas::{Nla, UnixDiagName},
9+
ShowFlags, StateFlags, UnixRequest, UnixResponse, UnixResponseBuffer,
10+
UnixResponseHeader,
1011
},
1112
};
1213

@@ -39,7 +40,7 @@ lazy_static! {
3940
cookie: [0xa0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
4041
},
4142
nlas: smallvec![
42-
Nla::Name("/tmp/.ICE-unix/1151".to_string()),
43+
Nla::Name(UnixDiagName::Pathname("/tmp/.ICE-unix/1151".into())),
4344
Nla::ReceiveQueueLength(0, 128),
4445
Nla::Shutdown(0),
4546
]
@@ -101,7 +102,7 @@ lazy_static! {
101102
cookie: [0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
102103
},
103104
nlas: smallvec![
104-
Nla::Name("/run/user/1000/bus".to_string()),
105+
Nla::Name(UnixDiagName::Pathname("/run/user/1000/bus".into())),
105106
Nla::Peer(31062),
106107
Nla::ReceiveQueueLength(0, 0),
107108
Nla::Shutdown(0),
@@ -165,3 +166,65 @@ fn emit_socket_info() {
165166
SOCKET_INFO.emit(&mut buf);
166167
assert_eq!(&buf[..], &SOCKET_INFO_BUF[..]);
167168
}
169+
170+
lazy_static! {
171+
static ref ABSTRACT_ADDRESS: UnixResponse = UnixResponse {
172+
header: UnixResponseHeader {
173+
kind: SOCK_STREAM,
174+
state: TCP_LISTEN,
175+
inode: 20238,
176+
cookie: [0xa0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
177+
},
178+
nlas: smallvec![
179+
Nla::Name(UnixDiagName::Abstract(
180+
"1c1440c5f5e2a52e/bus/systemd/\0/bus-api-user".into()
181+
)),
182+
Nla::ReceiveQueueLength(0, 128),
183+
Nla::Shutdown(0),
184+
]
185+
};
186+
}
187+
188+
#[rustfmt::skip]
189+
static ABSTRACT_ADDRESS_BUF: [u8; 84] = [
190+
0x01, // family: AF_UNIX
191+
0x01, // type: SOCK_STREAM
192+
0x0a, // state: TCP_LISTEN
193+
0x00, // padding
194+
0x0e, 0x4f, 0x00, 0x00, // inode number
195+
0xa0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // cookie
196+
197+
// NLAs
198+
0x30, 0x00, // length: 48
199+
0x00, 0x00, // type: UNIX_DIAG_NAME
200+
// value: \01c1440c5f5e2a52e/bus/systemd/\0/bus-api-user
201+
0x00, 0x31, 0x63, 0x31, 0x34, 0x34, 0x30, 0x63, 0x35, 0x66, 0x35, 0x65, 0x32, 0x61, 0x35, 0x32, 0x65, 0x2f, 0x62, 0x75, 0x73, 0x2f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x64, 0x2f, 0x00, 0x2f, 0x62, 0x75, 0x73, 0x2d, 0x61, 0x70, 0x69, 0x2d, 0x75, 0x73, 0x65, 0x72,
202+
203+
0x0c, 0x00, // length: 12
204+
0x04, 0x00, // type: UNIX_DIAG_RQLEN
205+
// value: ReceiveQueueLength(0, 128)
206+
0x00, 0x00, 0x00, 0x00,
207+
0x80, 0x00, 0x00, 0x00,
208+
209+
0x05, 0x00, // length: 5
210+
0x06, 0x00, // type: UNIX_DIAG_SHUTDOWN
211+
0x00, // value: 0
212+
0x00, 0x00, 0x00 // padding
213+
];
214+
215+
#[test]
216+
fn parse_abstract_address() {
217+
let parsed = UnixResponse::parse(
218+
&UnixResponseBuffer::new_checked(&&ABSTRACT_ADDRESS_BUF[..]).unwrap(),
219+
)
220+
.unwrap();
221+
assert_eq!(parsed, *ABSTRACT_ADDRESS);
222+
}
223+
224+
#[test]
225+
fn emit_abstract_address() {
226+
assert_eq!(ABSTRACT_ADDRESS.buffer_len(), 84);
227+
let mut buf = vec![0xff; ABSTRACT_ADDRESS.buffer_len()];
228+
ABSTRACT_ADDRESS.emit(&mut buf);
229+
assert_eq!(&buf[..], &ABSTRACT_ADDRESS_BUF[..]);
230+
}

0 commit comments

Comments
 (0)