Skip to content

Commit 5143534

Browse files
committed
Fix NULL-terminated strings in IOKit
When `const io_name_t` is used in arguments, it doesn't actually mean that the length of the input must be 128 characters long; that part is only a hint, the important thing is that the string is NULL-terminated. (When it's used without `const`, it's an output, and then the length _does_ matter. But that part we already handle correctly). See discussion on Matrix for context: https://matrix.to/#/!SrJvHgAPHenBakQHSz:matrix.org/$1VLn3ZskHtMuxGsP0TwPNnnLsNwTxxe_4bSs6WX1i_w?via=matrix.org&via=rymc.io&via=mozilla.org
1 parent c67f830 commit 5143534

7 files changed

Lines changed: 196 additions & 12 deletions

File tree

crates/header-translator/src/rust_type.rs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3015,10 +3015,20 @@ impl Ty {
30153015
}
30163016
}
30173017

3018+
/// Whether the type, if behind a pointer, is allowed to be converted
3019+
/// to/from `CStr`.
30183020
fn is_pointee_cstr(&self) -> bool {
30193021
// Only check `char`; `unsigned char` or `signed char` are not
30203022
// converted to `CStr` automatically.
3021-
matches!(self.through_typedef(), Self::Primitive(Primitive::Char))
3023+
match self.through_typedef() {
3024+
Self::Primitive(Primitive::Char) => true,
3025+
// `[c_char; N]` arrays work similar to `*c_char` in that we want
3026+
// to map them to `&CStr` when possible.
3027+
Self::Array { element_type, .. } => {
3028+
matches!(**element_type, Self::Primitive(Primitive::Char))
3029+
}
3030+
_ => false,
3031+
}
30223032
}
30233033

30243034
pub(crate) fn contains_union(&self) -> bool {
@@ -3732,7 +3742,7 @@ impl Ty {
37323742
})
37333743
}
37343744

3735-
pub(crate) fn fn_argument(&self, allow_generic_param: bool) -> impl fmt::Display + '_ {
3745+
fn fn_argument(&self, allow_generic_param: bool) -> impl fmt::Display + '_ {
37363746
FormatterFn(move |f| match self {
37373747
Self::Pointer {
37383748
nullability,
@@ -3972,17 +3982,29 @@ impl Ty {
39723982
bounds: PointerBounds::NullTerminated,
39733983
pointee,
39743984
} if pointee.is_pointee_cstr() => {
3985+
// We could emit a length check here for `char[N]`, to ensure
3986+
// that the user doesn't pass a string that is too long.
3987+
//
3988+
// But in practice, at least for the places that this is
3989+
// relevant (namely IOKit), it doesn't matter, the length is
3990+
// gotten via. `strlen`, and passing a string that is too long
3991+
// (seemingly) doesn't change the behaviour.
3992+
3993+
writeln!(f, "let {arg_to} = ")?;
39753994
if *nullability == Nullability::NonNull {
3976-
writeln!(
3977-
f,
3978-
"let {arg_to} = NonNull::new({arg}.as_ptr().cast_mut()).unwrap();"
3979-
)
3995+
writeln!(f, "NonNull::new({arg}.as_ptr().cast_mut()).unwrap()")?;
39803996
} else {
39813997
writeln!(
39823998
f,
3983-
"let {arg_to} = {arg}.map(|ptr| ptr.as_ptr()).unwrap_or_else(core::ptr::null);"
3984-
)
3999+
"{arg}.map(|ptr| ptr.as_ptr()).unwrap_or_else(core::ptr::null)"
4000+
)?;
39854001
}
4002+
if !matches!(pointee.through_typedef(), Self::Primitive(Primitive::Char)) {
4003+
writeln!(f, ".cast()")?;
4004+
}
4005+
writeln!(f, ";")?;
4006+
4007+
Ok(())
39864008
}
39874009
// HACK to support CFArray<T>.
39884010
Self::Pointer {
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[dev-dependencies]
2+
objc2-core-foundation = { workspace = true, features = ["CFString"] }

framework-crates/objc2-io-kit/Cargo.toml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

framework-crates/objc2-io-kit/src/consumes_argument.rs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#![allow(non_snake_case, clippy::missing_safety_doc)]
2-
use core::{ffi::c_void, ptr};
2+
use core::ffi::{c_void, CStr};
3+
use core::ptr;
34
use objc2_core_foundation::{CFDictionary, CFRetained};
45

56
use crate::{
@@ -23,6 +24,13 @@ fn consume(matching: Option<CFRetained<CFDictionary>>) -> *mut CFDictionary {
2324
/// Parameter `matching`: A CF dictionary containing matching information, of which one reference is always consumed by this function (Note prior to the Tiger release there was a small chance that the dictionary might not be released if there was an error attempting to serialize the dictionary). IOKitLib can construct matching dictionaries for common criteria with helper functions such as IOServiceMatching, IOServiceNameMatching, IOBSDNameMatching.
2425
///
2526
/// Returns: The first service matched is returned on success. The service must be released by the caller.
27+
///
28+
/// # Safety
29+
///
30+
/// - `matching` generic must be of the correct type.
31+
/// - `matching` generic must be of the correct type.
32+
/// - `matching` might not allow `None`.
33+
#[inline]
2634
pub unsafe fn IOServiceGetMatchingService(
2735
main_port: libc::mach_port_t,
2836
matching: Option<CFRetained<CFDictionary>>,
@@ -48,6 +56,14 @@ pub unsafe fn IOServiceGetMatchingService(
4856
/// Parameter `existing`: An iterator handle, or NULL, is returned on success, and should be released by the caller when the iteration is finished. If NULL is returned, the iteration was successful but found no matching services.
4957
///
5058
/// Returns: A kern_return_t error code.
59+
///
60+
/// # Safety
61+
///
62+
/// - `matching` generic must be of the correct type.
63+
/// - `matching` generic must be of the correct type.
64+
/// - `matching` might not allow `None`.
65+
/// - `existing` must be a valid pointer.
66+
#[inline]
5167
pub unsafe fn IOServiceGetMatchingServices(
5268
main_port: libc::mach_port_t,
5369
matching: Option<CFRetained<CFDictionary>>,
@@ -91,9 +107,21 @@ pub unsafe fn IOServiceGetMatchingServices(
91107
/// Parameter `notification`: An iterator handle is returned on success, and should be released by the caller when the notification is to be destroyed. The notification is armed when the iterator is emptied by calls to IOIteratorNext - when no more objects are returned, the notification is armed. Note the notification is not armed when first created.
92108
///
93109
/// Returns: A kern_return_t error code.
110+
///
111+
/// # Safety
112+
///
113+
/// - `notify_port` must be a valid pointer.
114+
/// - `notification_type` might not allow `None`.
115+
/// - `matching` generic must be of the correct type.
116+
/// - `matching` generic must be of the correct type.
117+
/// - `matching` might not allow `None`.
118+
/// - `callback` must be implemented correctly.
119+
/// - `ref_con` must be a valid pointer.
120+
/// - `notification` must be a valid pointer.
121+
#[inline]
94122
pub unsafe fn IOServiceAddMatchingNotification(
95123
notify_port: IONotificationPortRef,
96-
notification_type: *mut io_name_t,
124+
notification_type: Option<&CStr>,
97125
matching: Option<CFRetained<CFDictionary>>,
98126
callback: IOServiceMatchingCallback,
99127
ref_con: *mut c_void,
@@ -102,14 +130,18 @@ pub unsafe fn IOServiceAddMatchingNotification(
102130
extern "C-unwind" {
103131
fn IOServiceAddMatchingNotification(
104132
notify_port: IONotificationPortRef,
105-
notification_type: *mut io_name_t,
133+
notification_type: *const io_name_t,
106134
matching: *mut CFDictionary,
107135
callback: IOServiceMatchingCallback,
108136
ref_con: *mut c_void,
109137
notification: *mut io_iterator_t,
110138
) -> libc::kern_return_t;
111139
}
112140

141+
let notification_type = notification_type
142+
.map(|ptr| ptr.as_ptr())
143+
.unwrap_or_else(core::ptr::null)
144+
.cast();
113145
unsafe {
114146
IOServiceAddMatchingNotification(
115147
notify_port,
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#![cfg(feature = "libc")]
2+
3+
use std::ffi::{CStr, CString};
4+
5+
use libc::MACH_PORT_NULL;
6+
use objc2_io_kit::{
7+
kIOReturnSuccess, kIOServicePlane, IOObjectConformsTo, IOObjectCopyClass, IOObjectGetClass,
8+
IOObjectRelease, IORegistryEntryFromPath, IORegistryEntryGetPath, IORegistryGetRootEntry,
9+
};
10+
11+
#[cfg(not(target_os = "macos"))]
12+
macro_rules! main_port {
13+
() => {
14+
#[allow(unused_unsafe)]
15+
unsafe {
16+
objc2_io_kit::kIOMainPortDefault
17+
}
18+
};
19+
}
20+
21+
#[cfg(target_os = "macos")]
22+
macro_rules! main_port {
23+
() => {{
24+
#[allow(deprecated, unused_unsafe)]
25+
unsafe {
26+
objc2_io_kit::kIOMasterPortDefault
27+
}
28+
}};
29+
}
30+
31+
#[test]
32+
fn out_pointer() {
33+
let obj = IORegistryGetRootEntry(main_port!());
34+
35+
let mut name = [0; 128];
36+
assert_eq!(
37+
unsafe { IOObjectGetClass(obj, Some(&mut name)) },
38+
kIOReturnSuccess
39+
);
40+
let name = name.map(|c| c as u8);
41+
let name = CStr::from_bytes_until_nul(&name).unwrap();
42+
43+
let cf_name = IOObjectCopyClass(obj).unwrap();
44+
45+
assert_eq!(name.to_str().unwrap(), cf_name.to_string());
46+
assert_eq!(name, c"IORegistryEntry");
47+
48+
assert_eq!(IOObjectRelease(obj), kIOReturnSuccess);
49+
}
50+
51+
#[test]
52+
fn in_pointer() {
53+
let obj = IORegistryGetRootEntry(main_port!());
54+
55+
assert!(unsafe { IOObjectConformsTo(obj, Some(c"IORegistryEntry")) });
56+
assert!(!unsafe { IOObjectConformsTo(obj, Some(c"BogusClassName")) });
57+
58+
assert_eq!(IOObjectRelease(obj), kIOReturnSuccess);
59+
}
60+
61+
#[test]
62+
fn entry_path() {
63+
let path = c"IOService:/";
64+
65+
let obj = unsafe { IORegistryEntryFromPath(main_port!(), Some(path)) };
66+
assert_ne!(obj, MACH_PORT_NULL as u32);
67+
68+
let mut out_path = [0; 512];
69+
assert_eq!(
70+
unsafe { IORegistryEntryGetPath(obj, Some(kIOServicePlane), Some(&mut out_path)) },
71+
kIOReturnSuccess
72+
);
73+
let out_path = out_path.map(|c| c as u8);
74+
let out_path = CStr::from_bytes_until_nul(&out_path).unwrap();
75+
assert_eq!(out_path, path);
76+
77+
assert_eq!(IOObjectRelease(obj), kIOReturnSuccess);
78+
}
79+
80+
#[test]
81+
fn entry_path_too_long() {
82+
let path = CString::new([b'x'; 1000]).unwrap();
83+
let obj = unsafe { IORegistryEntryFromPath(main_port!(), Some(&path)) };
84+
assert_eq!(obj, MACH_PORT_NULL as u32);
85+
}
86+
87+
#[test]
88+
fn in_pointer_too_long() {
89+
let obj = IORegistryGetRootEntry(main_port!());
90+
91+
let name = CString::new([b'x'; 1000]).unwrap();
92+
assert!(!unsafe { IOObjectConformsTo(obj, Some(&name)) });
93+
94+
assert_eq!(IOObjectRelease(obj), kIOReturnSuccess);
95+
}

framework-crates/objc2-io-kit/translation-config.toml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,36 @@ fn.IOCFUnserialize.arguments.3.lifetime = "out-pointer-retained"
251251
fn.IOCFUnserializeBinary.arguments.4.lifetime = "out-pointer-retained"
252252
fn.IOCFUnserializeWithSize.arguments.4.lifetime = "out-pointer-retained"
253253

254+
##
255+
## Pointer bounds
256+
##
257+
258+
fn.IOObjectConformsTo.arguments.1.bounds = "null-terminated"
259+
fn.IOServiceAddNotification.arguments.1.bounds = "null-terminated"
260+
fn.IOServiceAddMatchingNotification.arguments.1.bounds = "null-terminated"
261+
fn.IOServiceAddInterestNotification.arguments.2.bounds = "null-terminated"
262+
fn.IORegistryEntryFromPath.arguments.1.bounds = "null-terminated"
263+
fn.IORegistryCreateIterator.arguments.1.bounds = "null-terminated"
264+
fn.IORegistryEntryCreateIterator.arguments.1.bounds = "null-terminated"
265+
fn.IORegistryEntryGetNameInPlane.arguments.1.bounds = "null-terminated"
266+
fn.IORegistryEntryGetLocationInPlane.arguments.1.bounds = "null-terminated"
267+
fn.IORegistryEntryGetPath.arguments.1.bounds = "null-terminated"
268+
fn.IORegistryEntryCopyPath.arguments.1.bounds = "null-terminated"
269+
fn.IORegistryEntrySearchCFProperty.arguments.1.bounds = "null-terminated"
270+
fn.IORegistryEntryGetProperty.arguments.1.bounds = "null-terminated"
271+
fn.IORegistryEntryGetProperty.arguments.2.bounds = "single" # NOT sized by *size
272+
# fn.IORegistryEntryGetProperty.arguments.3.bounds = "single"
273+
fn.IORegistryEntryGetChildIterator.arguments.1.bounds = "null-terminated"
274+
fn.IORegistryEntryGetChildEntry.arguments.1.bounds = "null-terminated"
275+
fn.IORegistryEntryGetParentIterator.arguments.1.bounds = "null-terminated"
276+
fn.IORegistryEntryGetParentEntry.arguments.1.bounds = "null-terminated"
277+
fn.IORegistryEntryInPlane.arguments.1.bounds = "null-terminated"
278+
fn.IOServiceOFPathToBSDName.arguments.1.bounds = "null-terminated"
279+
280+
# network
281+
fn.IONetworkSetPacketFiltersMask.arguments.1.bounds = "null-terminated"
282+
fn.IONetworkGetPacketFiltersMask.arguments.1.bounds = "null-terminated"
283+
254284
##
255285
## Safety
256286
##

generated

0 commit comments

Comments
 (0)