Skip to content

Commit 673c96b

Browse files
committed
Return errors instead of panicking on strings with interior NUL
1 parent 5ce0ff4 commit 673c96b

7 files changed

Lines changed: 157 additions & 43 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
Non-UTF-8 property types no longer panic.
2929
- `Client::property_get`, `property_get_subject` and `property_get_all` no longer panic on
3030
non-UTF-8 keys, values or types. Invalid bytes are replaced with `U+FFFD`.
31+
- Added `Error::InteriorNul`. Methods that take strings now return it instead of panicking
32+
when a string contains a NUL byte.
3133
- `PropertyChange` keys are now `Option<Cow<'a, str>>` and `PropertyChangeOwned`
3234
keys are now `Option<String>`. The key is `None` when all properties of a subject were removed
3335
(`Client::property_remove_subject`) or all properties were removed

‎src/client/client_impl.rs‎

Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::{ffi, fmt, ptr};
55

66
use crate::client::common::CREATE_OR_DESTROY_CLIENT_MUTEX;
77
use crate::jack_enums::CodeOrMessage;
8-
use crate::jack_utils::collect_strs;
8+
use crate::jack_utils::{collect_strs, to_cstring};
99
use crate::properties::PropertyChangeHandler;
1010
use crate::transport::Transport;
1111
use crate::{
@@ -120,24 +120,23 @@ impl Client {
120120
return Err(Error::LibraryError(err.to_string()));
121121
}
122122

123+
let client_name = to_cstring(client_name)?;
124+
let server_name = server_name.map(to_cstring).transpose()?;
125+
123126
crate::logging::maybe_init_logging();
124127
let mut status_bits = 0;
125-
let client_name = ffi::CString::new(client_name).unwrap();
126128
let client = match server_name {
127129
None => unsafe {
128130
j::jack_client_open(client_name.as_ptr(), options.bits(), &mut status_bits)
129131
},
130-
Some(server_name) => {
131-
let server_name = ffi::CString::new(server_name).unwrap();
132-
unsafe {
133-
j::jack_client_open_with_server_name(
134-
client_name.as_ptr(),
135-
options.bits(),
136-
&mut status_bits,
137-
server_name.as_ptr(),
138-
)
139-
}
140-
}
132+
Some(server_name) => unsafe {
133+
j::jack_client_open_with_server_name(
134+
client_name.as_ptr(),
135+
options.bits(),
136+
&mut status_bits,
137+
server_name.as_ptr(),
138+
)
139+
},
141140
};
142141
let status = ClientStatus::from_bits(status_bits).unwrap_or_else(ClientStatus::empty);
143142
if client.is_null() {
@@ -244,7 +243,7 @@ impl Client {
244243
raw: *mut jack_sys::jack_client_t,
245244
name: &str,
246245
) -> Option<jack_sys::jack_uuid_t> {
247-
let name = ffi::CString::new(name).unwrap();
246+
let name = ffi::CString::new(name).ok()?;
248247
let mut uuid: jack_sys::jack_uuid_t = Default::default();
249248
unsafe {
250249
let uuid_s = jack_sys::jack_get_uuid_for_client_name(raw, name.as_ptr());
@@ -294,7 +293,7 @@ impl Client {
294293

295294
/// Get the name of a client by its `&str` uuid.
296295
pub fn name_by_uuid_str(&self, uuid: &str) -> Option<String> {
297-
let uuid = ffi::CString::new(uuid).unwrap();
296+
let uuid = ffi::CString::new(uuid).ok()?;
298297
unsafe { self.name_by_uuid_raw(uuid.as_ptr()) }
299298
}
300299

@@ -316,8 +315,14 @@ impl Client {
316315
type_name_pattern: Option<&str>,
317316
flags: PortFlags,
318317
) -> Vec<String> {
319-
let port_name_pattern_cstr = ffi::CString::new(port_name_pattern.unwrap_or("")).unwrap();
320-
let type_name_pattern_cstr = ffi::CString::new(type_name_pattern.unwrap_or("")).unwrap();
318+
// A pattern containing a NUL byte can't be passed to JACK, and no port name or type
319+
// contains one, so nothing matches.
320+
let (Ok(port_name_pattern_cstr), Ok(type_name_pattern_cstr)) = (
321+
ffi::CString::new(port_name_pattern.unwrap_or("")),
322+
ffi::CString::new(type_name_pattern.unwrap_or("")),
323+
) else {
324+
return Vec::new();
325+
};
321326
let flags = libc::c_ulong::from(flags.bits());
322327
unsafe {
323328
let ports = j::jack_get_ports(
@@ -352,8 +357,8 @@ impl Client {
352357
if port_spec.jack_port_type().is_empty() {
353358
return Err(Error::PortRegistrationError(port_name.to_string()));
354359
}
355-
let port_name_c = ffi::CString::new(port_name).unwrap();
356-
let port_type_c = ffi::CString::new(port_spec.jack_port_type()).unwrap();
360+
let port_name_c = to_cstring(port_name)?;
361+
let port_type_c = to_cstring(port_spec.jack_port_type())?;
357362
let port_flags = port_spec.jack_flags().bits();
358363
let buffer_size = port_spec.jack_buffer_size();
359364
let pp = unsafe {
@@ -384,7 +389,8 @@ impl Client {
384389

385390
/// Get a `Port` by its port name.
386391
pub fn port_by_name(&self, port_name: &str) -> Option<Port<Unowned>> {
387-
let port_name = ffi::CString::new(port_name).unwrap();
392+
// No port name contains a NUL byte.
393+
let port_name = ffi::CString::new(port_name).ok()?;
388394
let pp = unsafe { j::jack_port_by_name(self.raw(), port_name.as_ptr()) };
389395
if pp.is_null() {
390396
None
@@ -412,9 +418,9 @@ impl Client {
412418
client_bin_name: &str,
413419
client_args: &str,
414420
) -> Result<InternalClientID, Error> {
415-
let ffi_client_name = ffi::CString::new(client_name).unwrap();
416-
let ffi_client_bin = ffi::CString::new(client_bin_name).unwrap();
417-
let ffi_client_args = ffi::CString::new(client_args).unwrap();
421+
let ffi_client_name = to_cstring(client_name)?;
422+
let ffi_client_bin = to_cstring(client_bin_name)?;
423+
let ffi_client_args = to_cstring(client_args)?;
418424

419425
let mut status_bits = 0;
420426
let options: j::Enum_JackOptions = j::JackLoadName | j::JackLoadInit;
@@ -506,7 +512,7 @@ impl Client {
506512
port_name: &str,
507513
enable_monitor: bool,
508514
) -> Result<(), Error> {
509-
let port_name_cstr = ffi::CString::new(port_name).unwrap();
515+
let port_name_cstr = to_cstring(port_name)?;
510516
let res = unsafe {
511517
j::jack_port_request_monitor_by_name(
512518
self.raw(),
@@ -558,15 +564,15 @@ impl Client {
558564
/// 3. The port flags of the `destination_port` must include `IS_INPUT`.
559565
/// 4. Both ports must be owned by active clients.
560566
///
561-
/// # Panics
562-
/// Panics if it is not possible to convert `source_port` or `destination_port` to a `CString`.
567+
/// Returns `Err(Error::InteriorNul)` if `source_port` or `destination_port` contains a NUL
568+
/// byte.
563569
pub fn connect_ports_by_name(
564570
&self,
565571
source_port: &str,
566572
destination_port: &str,
567573
) -> Result<(), Error> {
568-
let source_cstr = ffi::CString::new(source_port).unwrap();
569-
let destination_cstr = ffi::CString::new(destination_port).unwrap();
574+
let source_cstr = to_cstring(source_port)?;
575+
let destination_cstr = to_cstring(destination_port)?;
570576
let res =
571577
unsafe { j::jack_connect(self.raw(), source_cstr.as_ptr(), destination_cstr.as_ptr()) };
572578
match res {
@@ -661,8 +667,8 @@ impl Client {
661667
source_port: &str,
662668
destination_port: &str,
663669
) -> Result<(), Error> {
664-
let source_port = ffi::CString::new(source_port).unwrap();
665-
let destination_port = ffi::CString::new(destination_port).unwrap();
670+
let source_port = to_cstring(source_port)?;
671+
let destination_port = to_cstring(destination_port)?;
666672
let res = unsafe {
667673
j::jack_disconnect(self.raw(), source_port.as_ptr(), destination_port.as_ptr())
668674
};
@@ -674,12 +680,16 @@ impl Client {
674680

675681
/// The buffer size of a port type
676682
///
683+
/// Returns 0 if `port_type` contains a NUL byte, since no port type does.
684+
///
677685
/// # Safety
678686
///
679687
/// * This function may only be called in a buffer size callback.
680688
pub unsafe fn type_buffer_size(&self, port_type: &str) -> usize {
681689
unsafe {
682-
let port_type = ffi::CString::new(port_type).unwrap();
690+
let Ok(port_type) = ffi::CString::new(port_type) else {
691+
return 0;
692+
};
683693
j::jack_port_type_get_buffer_size(self.raw(), port_type.as_ptr())
684694
}
685695
}

‎src/jack_enums.rs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ pub enum Error {
3333
ClientIsNoLongerAlive,
3434
ClientPanicked,
3535
RingbufferCreateFailed,
36+
/// A string argument contained an interior NUL byte, so it cannot be passed to JACK.
37+
InteriorNul(String),
3638
UnknownError {
3739
error_code: libc::c_int,
3840
},
@@ -85,6 +87,7 @@ impl std::fmt::Display for Error {
8587
Error::ClientIsNoLongerAlive => write!(f, "client is no longer alive"),
8688
Error::ClientPanicked => write!(f, "client notifcation or processor panicked"),
8789
Error::RingbufferCreateFailed => write!(f, "ringbuffer creation failed"),
90+
Error::InteriorNul(s) => write!(f, "string {s:?} contains an interior NUL byte"),
8891
Error::UnknownError { error_code } => write!(f, "unkown error with code {error_code}"),
8992
}
9093
}

‎src/jack_utils.rs‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
use jack_sys as j;
22
use std::ffi;
33

4+
use crate::Error;
5+
6+
/// Converts `s` to a `CString`, returning `Error::InteriorNul` if `s` contains a NUL byte.
7+
pub(crate) fn to_cstring(s: &str) -> Result<ffi::CString, Error> {
8+
ffi::CString::new(s).map_err(|_| Error::InteriorNul(s.to_string()))
9+
}
10+
411
/// Collects strings from an array of c-strings into a Rust vector of strings
512
/// and frees the memory pointed to by `ptr`. The end of the array is marked by
613
/// the value of the c-string being the null pointer. `ptr` may be `null`, in

‎src/port/port_impl.rs‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::marker::Sized;
77
use std::sync::Arc;
88
use std::{ffi, fmt, iter};
99

10+
use crate::jack_utils::to_cstring;
1011
use crate::{ClientHandle, Error, Frames, LatencyType, PortFlags};
1112

1213
lazy_static! {
@@ -120,7 +121,10 @@ impl<PS> Port<PS> {
120121
/// name `port_name`.
121122
pub fn is_connected_to(&self, port_name: &str) -> Result<bool, Error> {
122123
let res = unsafe {
123-
let port_name = ffi::CString::new(port_name).unwrap();
124+
// No port name contains a NUL byte.
125+
let Ok(port_name) = ffi::CString::new(port_name) else {
126+
return Ok(false);
127+
};
124128
j::jack_port_connected_to(self.raw(), port_name.as_ptr())
125129
};
126130
match res {
@@ -182,7 +186,7 @@ impl<PS> Port<PS> {
182186
/// Set's the short name of the port. If the full name is longer than `PORT_NAME_SIZE`, then it
183187
/// will be truncated.
184188
pub fn set_name(&mut self, short_name: &str) -> Result<(), Error> {
185-
let short_name = ffi::CString::new(short_name).unwrap();
189+
let short_name = to_cstring(short_name)?;
186190
let res = unsafe { j::jack_port_set_name(self.raw(), short_name.as_ptr()) };
187191
match res {
188192
0 => Ok(()),
@@ -201,7 +205,7 @@ impl<PS> Port<PS> {
201205
/// Ports can have up to two aliases - if both are already set, this function will return an
202206
/// error.
203207
pub fn set_alias(&mut self, alias: &str) -> Result<(), Error> {
204-
let alias = ffi::CString::new(alias).unwrap();
208+
let alias = to_cstring(alias)?;
205209
let res = unsafe { j::jack_port_set_alias(self.raw(), alias.as_ptr()) };
206210
match res {
207211
0 => Ok(()),
@@ -213,7 +217,7 @@ impl<PS> Port<PS> {
213217
///
214218
/// After a successful call, `alias` can no longer be used as an alternate name for `self`.
215219
pub fn unset_alias(&mut self, alias: &str) -> Result<(), Error> {
216-
let alias = ffi::CString::new(alias).unwrap();
220+
let alias = to_cstring(alias)?;
217221
let res = unsafe { j::jack_port_unset_alias(self.raw(), alias.as_ptr()) };
218222
match res {
219223
0 => Ok(()),

‎src/properties.rs‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ pub use metadata::*;
8585
mod metadata {
8686
use super::{PropertyChange, PropertyChangeHandler, j, uuid};
8787
use crate::Error;
88+
use crate::jack_utils::to_cstring;
8889
use std::{
8990
collections::HashMap,
9091
ffi,
@@ -231,7 +232,8 @@ mod metadata {
231232
/// * `subject` - The subject of the property.
232233
/// * `key` - The key of the property, a URI String.
233234
pub fn property_get(&self, subject: uuid, key: &str) -> Option<Property> {
234-
let key = ffi::CString::new(key).expect("key to be convert to CString");
235+
// No key contains a NUL byte.
236+
let key = ffi::CString::new(key).ok()?;
235237
let mut value: MaybeUninit<*mut ::libc::c_char> = MaybeUninit::uninit();
236238
let mut typ: MaybeUninit<*mut ::libc::c_char> = MaybeUninit::uninit();
237239

@@ -316,12 +318,11 @@ mod metadata {
316318
key: &str,
317319
property: &Property,
318320
) -> Result<(), Error> {
319-
let key = ffi::CString::new(key).expect("to create cstring from key");
320-
let value =
321-
ffi::CString::new(property.value.as_str()).expect("to create cstring from value");
321+
let key = to_cstring(key)?;
322+
let value = to_cstring(&property.value)?;
323+
let typ = property.typ().map(to_cstring).transpose()?;
322324
map_error(|| unsafe {
323-
if let Some(t) = property.typ() {
324-
let t = ffi::CString::new(t).unwrap();
325+
if let Some(t) = &typ {
325326
j::jack_set_property(
326327
self.raw(),
327328
subject,
@@ -348,7 +349,7 @@ mod metadata {
348349
/// * `subject` - The subject to remove all properties from.
349350
/// * `key` - The key of the property to be removed. A URI string.
350351
pub fn property_remove(&self, subject: uuid, key: &str) -> Result<(), Error> {
351-
let key = ffi::CString::new(key).expect("to create cstring from key");
352+
let key = to_cstring(key)?;
352353
map_error(|| unsafe { j::jack_remove_property(self.raw(), subject, key.as_ptr()) })
353354
}
354355

‎src/tests/client.rs‎

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::tests::DEFAULT_TEST_CLIENT;
1+
use crate::{Error::InteriorNul, tests::DEFAULT_TEST_CLIENT};
22

33
#[test]
44
fn client_can_open() {
@@ -242,3 +242,90 @@ fn non_utf8_client_registration_does_not_stop_processing() {
242242
assert!(cycles.load(Ordering::Relaxed) > before);
243243
ac.deactivate().unwrap();
244244
}
245+
246+
#[test]
247+
fn client_open_rejects_interior_nul() {
248+
let nul = "a\0b";
249+
let options = crate::ClientOptions::default();
250+
assert_eq!(
251+
crate::Client::new(nul, options).err(),
252+
Some(InteriorNul(nul.to_string()))
253+
);
254+
assert_eq!(
255+
crate::Client::new_with_server_name("ok", nul, options).err(),
256+
Some(InteriorNul(nul.to_string()))
257+
);
258+
}
259+
260+
#[test]
261+
fn client_methods_reject_interior_nul() {
262+
let nul = "a\0b";
263+
let client = &*DEFAULT_TEST_CLIENT;
264+
assert_eq!(
265+
client.register_port(nul, crate::AudioIn::default()).err(),
266+
Some(InteriorNul(nul.to_string()))
267+
);
268+
assert_eq!(
269+
client.request_monitor_by_name(nul, true).err(),
270+
Some(InteriorNul(nul.to_string()))
271+
);
272+
assert_eq!(
273+
client.connect_ports_by_name(nul, "x").err(),
274+
Some(InteriorNul(nul.to_string()))
275+
);
276+
assert_eq!(
277+
client.connect_ports_by_name("x", nul).err(),
278+
Some(InteriorNul(nul.to_string()))
279+
);
280+
assert_eq!(
281+
client.disconnect_ports_by_name(nul, "x").err(),
282+
Some(InteriorNul(nul.to_string()))
283+
);
284+
assert_eq!(
285+
client.load_internal_client(nul, "x", "").err(),
286+
Some(InteriorNul(nul.to_string()))
287+
);
288+
}
289+
290+
#[test]
291+
fn property_methods_reject_interior_nul() {
292+
let nul = "a\0b";
293+
let client = &*DEFAULT_TEST_CLIENT;
294+
let uuid = client.uuid();
295+
let nul_key = client.property_set(uuid, nul, &crate::Property::new("v", None));
296+
assert_eq!(nul_key.err(), Some(InteriorNul(nul.to_string())));
297+
let nul_value = client.property_set(uuid, "k", &crate::Property::new(nul, None));
298+
assert_eq!(nul_value.err(), Some(InteriorNul(nul.to_string())));
299+
let nul_type = client.property_set(uuid, "k", &crate::Property::new("v", Some(nul.into())));
300+
assert_eq!(nul_type.err(), Some(InteriorNul(nul.to_string())));
301+
assert_eq!(
302+
client.property_remove(uuid, nul).err(),
303+
Some(InteriorNul(nul.to_string()))
304+
);
305+
}
306+
307+
#[test]
308+
fn lookups_with_interior_nul_find_nothing() {
309+
let nul = "a\0b";
310+
let client = &*DEFAULT_TEST_CLIENT;
311+
let flags = crate::PortFlags::empty();
312+
assert!(client.port_by_name(nul).is_none());
313+
assert!(client.uuid_of_client_by_name(nul).is_none());
314+
assert!(client.name_by_uuid_str(nul).is_none());
315+
assert!(client.property_get(client.uuid(), nul).is_none());
316+
assert!(client.ports(Some(nul), None, flags).is_empty());
317+
assert!(client.ports(None, Some(nul), flags).is_empty());
318+
}
319+
320+
#[test]
321+
fn port_methods_reject_interior_nul() {
322+
let nul = "a\0b";
323+
let (client, _) = crate::Client::new("", crate::ClientOptions::default()).unwrap();
324+
let mut port = client
325+
.register_port("p", crate::AudioIn::default())
326+
.unwrap();
327+
assert_eq!(port.set_name(nul), Err(InteriorNul(nul.to_string())));
328+
assert_eq!(port.set_alias(nul), Err(InteriorNul(nul.to_string())));
329+
assert_eq!(port.unset_alias(nul), Err(InteriorNul(nul.to_string())));
330+
assert_eq!(port.is_connected_to(nul), Ok(false));
331+
}

0 commit comments

Comments
 (0)