Skip to content

Commit bcbdb89

Browse files
authored
Fix GL X11 error handling (#181)
This PR fixes a soundness issue inside the `XErrorHandler` utility function, which could use an xlib `Display` pointer that had no guarantee to be still valid. This could happen because the underlying `XErrorEvent` was stored directly inside the returned error type, and the `Display` pointer it contained was only called to extract the error message during in `Debug` implementation, which could happen long after the associated `Display` had been destroyed. This PR fixes this by extracting the error message upfront and storing it as a string as soon as the error happens. This PR also fixes the `handle` method that was not properly marked `unsafe`.
1 parent f5b0c6d commit bcbdb89

File tree

2 files changed

+77
-33
lines changed

2 files changed

+77
-33
lines changed

src/gl/x11.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -229,12 +229,12 @@ impl GlContext {
229229
}
230230

231231
pub fn swap_buffers(&self) {
232-
errors::XErrorHandler::handle(self.display, |error_handler| {
233-
unsafe {
232+
unsafe {
233+
errors::XErrorHandler::handle(self.display, |error_handler| {
234234
glx::glXSwapBuffers(self.display, self.window);
235-
}
236-
error_handler.check().unwrap();
237-
})
235+
error_handler.check().unwrap();
236+
})
237+
}
238238
}
239239
}
240240

src/gl/x11/errors.rs

Lines changed: 72 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,27 @@
11
use std::ffi::CStr;
2-
use std::fmt::{Debug, Formatter};
2+
use std::fmt::{Debug, Display, Formatter};
33
use x11::xlib;
44

55
use std::cell::RefCell;
6+
use std::error::Error;
7+
use std::os::raw::{c_int, c_uchar, c_ulong};
68
use std::panic::AssertUnwindSafe;
79

810
thread_local! {
9-
/// Used as part of [`XerrorHandler::handle()`]. When an X11 error occurs during this function,
11+
/// Used as part of [`XErrorHandler::handle()`]. When an X11 error occurs during this function,
1012
/// the error gets copied to this RefCell after which the program is allowed to resume. The
11-
/// error can then be converted to a regular Rust Result value afterwards.
12-
static CURRENT_X11_ERROR: RefCell<Option<xlib::XErrorEvent>> = RefCell::new(None);
13+
/// error can then be converted to a regular Rust Result value afterward.
14+
static CURRENT_X11_ERROR: RefCell<Option<XLibError>> = const { RefCell::new(None) };
1315
}
1416

1517
/// A helper struct for safe X11 error handling
1618
pub struct XErrorHandler<'a> {
1719
display: *mut xlib::Display,
18-
error: &'a RefCell<Option<xlib::XErrorEvent>>,
20+
error: &'a RefCell<Option<XLibError>>,
1921
}
2022

2123
impl<'a> XErrorHandler<'a> {
22-
/// Syncs and checks if any previous X11 calls returned an error
24+
/// Syncs and checks if any previous X11 calls from the given display returned an error
2325
pub fn check(&mut self) -> Result<(), XLibError> {
2426
// Flush all possible previous errors
2527
unsafe {
@@ -29,20 +31,27 @@ impl<'a> XErrorHandler<'a> {
2931

3032
match error {
3133
None => Ok(()),
32-
Some(inner) => Err(XLibError { inner }),
34+
Some(inner) => Err(inner),
3335
}
3436
}
3537

3638
/// Sets up a temporary X11 error handler for the duration of the given closure, and allows
37-
/// that closure to check on the latest X11 error at any time
38-
pub fn handle<T, F: FnOnce(&mut XErrorHandler) -> T>(
39+
/// that closure to check on the latest X11 error at any time.
40+
///
41+
/// # Safety
42+
///
43+
/// The given display pointer *must* be and remain valid for the duration of this function, as
44+
/// well as for the duration of the given `handler` closure.
45+
pub unsafe fn handle<T, F: FnOnce(&mut XErrorHandler) -> T>(
3946
display: *mut xlib::Display, handler: F,
4047
) -> T {
48+
/// # Safety
49+
/// The given display and error pointers *must* be valid for the duration of this function.
4150
unsafe extern "C" fn error_handler(
4251
_dpy: *mut xlib::Display, err: *mut xlib::XErrorEvent,
4352
) -> i32 {
44-
// SAFETY: the error pointer should be safe to copy
45-
let err = *err;
53+
// SAFETY: the error pointer should be safe to access
54+
let err = &*err;
4655

4756
CURRENT_X11_ERROR.with(|error| {
4857
let mut error = error.borrow_mut();
@@ -51,7 +60,7 @@ impl<'a> XErrorHandler<'a> {
5160
// cause of the other errors
5261
Some(_) => 1,
5362
None => {
54-
*error = Some(err);
63+
*error = Some(XLibError::from_event(err));
5564
0
5665
}
5766
}
@@ -65,7 +74,9 @@ impl<'a> XErrorHandler<'a> {
6574

6675
CURRENT_X11_ERROR.with(|error| {
6776
// Make sure to clear any errors from the last call to this function
68-
*error.borrow_mut() = None;
77+
{
78+
*error.borrow_mut() = None;
79+
}
6980

7081
let old_handler = unsafe { xlib::XSetErrorHandler(Some(error_handler)) };
7182
let panic_result = std::panic::catch_unwind(AssertUnwindSafe(|| {
@@ -84,39 +95,72 @@ impl<'a> XErrorHandler<'a> {
8495
}
8596

8697
pub struct XLibError {
87-
inner: xlib::XErrorEvent,
98+
type_: c_int,
99+
resourceid: xlib::XID,
100+
serial: c_ulong,
101+
error_code: c_uchar,
102+
request_code: c_uchar,
103+
minor_code: c_uchar,
104+
105+
display_name: Box<str>,
88106
}
89107

90108
impl XLibError {
91-
pub fn get_display_name(&self, buf: &mut [u8]) -> &CStr {
109+
/// # Safety
110+
/// The display pointer inside error must be valid for the duration of this call
111+
unsafe fn from_event(error: &xlib::XErrorEvent) -> Self {
112+
Self {
113+
type_: error.type_,
114+
resourceid: error.resourceid,
115+
serial: error.serial,
116+
117+
error_code: error.error_code,
118+
request_code: error.request_code,
119+
minor_code: error.minor_code,
120+
121+
display_name: Self::get_display_name(error),
122+
}
123+
}
124+
125+
/// # Safety
126+
/// The display pointer inside error must be valid for the duration of this call
127+
unsafe fn get_display_name(error: &xlib::XErrorEvent) -> Box<str> {
128+
let mut buf = [0; 255];
92129
unsafe {
93130
xlib::XGetErrorText(
94-
self.inner.display,
95-
self.inner.error_code.into(),
131+
error.display,
132+
error.error_code.into(),
96133
buf.as_mut_ptr().cast(),
97134
(buf.len() - 1) as i32,
98135
);
99136
}
100137

101138
*buf.last_mut().unwrap() = 0;
102139
// SAFETY: whatever XGetErrorText did or not, we guaranteed there is a nul byte at the end of the buffer
103-
unsafe { CStr::from_ptr(buf.as_mut_ptr().cast()) }
140+
let cstr = unsafe { CStr::from_ptr(buf.as_mut_ptr().cast()) };
141+
142+
cstr.to_string_lossy().into()
104143
}
105144
}
106145

107146
impl Debug for XLibError {
108147
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
109-
let mut buf = [0; 255];
110-
let display_name = self.get_display_name(&mut buf).to_string_lossy();
111-
112148
f.debug_struct("XLibError")
113-
.field("error_code", &self.inner.error_code)
114-
.field("error_message", &display_name)
115-
.field("minor_code", &self.inner.minor_code)
116-
.field("request_code", &self.inner.request_code)
117-
.field("type", &self.inner.type_)
118-
.field("resource_id", &self.inner.resourceid)
119-
.field("serial", &self.inner.serial)
149+
.field("error_code", &self.error_code)
150+
.field("error_message", &self.display_name)
151+
.field("minor_code", &self.minor_code)
152+
.field("request_code", &self.request_code)
153+
.field("type", &self.type_)
154+
.field("resource_id", &self.resourceid)
155+
.field("serial", &self.serial)
120156
.finish()
121157
}
122158
}
159+
160+
impl Display for XLibError {
161+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
162+
write!(f, "XLib error: {} (error code {})", &self.display_name, self.error_code)
163+
}
164+
}
165+
166+
impl Error for XLibError {}

0 commit comments

Comments
 (0)