-
Notifications
You must be signed in to change notification settings - Fork 38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
There is a possible race around generate_id and the use of the XC-MISC extension #516
Comments
I can only think of two ways of avoiding this:
|
Option 1 requires people to carefully write code. Aka "someone will get it wrong". Option 2 is not thread-safe (although I'd say this race is quite unlikely... I'd be curious to learn more about the C code that originally caused the issue with libX11...) |
Dunno why, but I wrote a program to reproduce this (at least I found #783 in the process). The actual bug is hit in Output:
use x11rb::connection::Connection;
use x11rb::errors::ReplyOrIdError;
use x11rb::protocol::xproto;
use x11rb::wrapper::ConnectionExt;
const IDS_TO_KEEP_AVAILABLE: u32 = 1;
const NUM_THREADS: usize = 4;
fn create_pixmap(conn: &impl Connection, screen: usize) -> Result<u32, ReplyOrIdError> {
let id = conn.generate_id()?;
xproto::create_pixmap(conn, 1, id, conn.setup().roots[screen].root, 1, 1)?;
Ok(id)
}
// Check that we really only have IDS_TO_KEEP_AVAILABLE XIDs left
fn check_id_exhaustion<C>(conn: &C, screen: usize) -> Result<(), ReplyOrIdError>
where
C: Connection + x11rb::connection::RequestConnection,
{
// Allocate all available IDs. This uses PixmapWrapper to free the pixmaps again automatically.
let ids = (0..=IDS_TO_KEEP_AVAILABLE)
.map(|_| {
create_pixmap(conn, screen)
.map(|pixmap| xproto::PixmapWrapper::for_pixmap(conn, pixmap))
})
.collect::<Result<Vec<_>, _>>()?;
match conn.generate_id() {
Err(x11rb::errors::ReplyOrIdError::IdsExhausted) => {
// Yup, that's the error we want to see!
}
Ok(_) => panic!("Expected ID allocation to fail, but it succeeded!?"),
Err(err) => panic!("ID allocation failed unexpectedly with {err:?}"),
}
// Now free the pixmaps again (silences a compiler warning)
drop(ids);
Ok(())
}
fn worker_thread<C>(conn: &C, screen: usize) -> Result<(), ReplyOrIdError>
where
C: Connection + x11rb::connection::RequestConnection,
{
let id = conn.generate_id();
// Ignore IdsExhausted errors
let id = match id {
Err(x11rb::errors::ReplyOrIdError::IdsExhausted) => {
println!("One thread sees IdsExhausted, this is not supposed to happen");
return Ok(());
}
id => id?,
};
// How to make a race more likely? Insert a sleep!
std::thread::sleep(std::time::Duration::from_micros(100));
xproto::create_pixmap(conn, 1, id, conn.setup().roots[screen].root, 1, 1)?;
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (conn, screen) = x11rb::connect(None)?;
let setup = conn.setup();
assert_eq!(
1,
setup.resource_id_mask & 1,
"I am too lazy to figure out what to do with shifted masks"
);
let num_ids_available = setup.resource_id_mask;
let to_allocate = num_ids_available - IDS_TO_KEEP_AVAILABLE;
println!("Allocating {to_allocate} pixmaps");
for num in 0..to_allocate {
create_pixmap(&conn, screen)?;
if num % 100_000 == 0 {
println!("..allocated {num} pixmaps");
}
}
println!("Checking whether we exhausted XIDs correctly...");
check_id_exhaustion(&conn, screen)?;
println!("Trying to cause BadIdChoice with {NUM_THREADS} threads...");
// Now start threads that allocate and free things
std::thread::scope(|s| {
let handles = (0..NUM_THREADS)
.map(|_| s.spawn(|| worker_thread(&conn, screen)))
.collect::<Vec<_>>();
handles
.into_iter()
.map(|handle| handle.join().unwrap())
.collect::<Result<Vec<_>, ReplyOrIdError>>()
})?;
conn.sync()?;
let event = conn.poll_for_event()?;
assert!(event.is_none(), "{event:?}");
Ok(())
} |
I just saw https://gitlab.freedesktop.org/xorg/lib/libxcb/-/issues/51 and https://gitlab.freedesktop.org/xorg/lib/libx11/-/issues/118. Basically:
If you do
let (a, b) = (conn.generate_id()?, conn.generate_id()?);
, thena == b
might hold. The first call returns the last available ID number, thus the XC-MISC extension is used to request a new batch of free IDs. Since the IDa
is still unused from the POV of the server, the new range might includea
again, thus a later call togenerate_id()
could returna
again, causing an XID collision /BadIDChoice
error. (But the way I describe it above witha == b
is very, very unlikely...)The text was updated successfully, but these errors were encountered: