Feb 25, 2026·6 min read·5 visits
The `hexchat` Rust crate allows plugins to deregister commands from within the command's own callback, triggering a Use-After-Free. Additionally, it exposes thread-unsafe macros as safe. The crate is unmaintained; no fix exists.
Rust promises memory safety, but when you dance with C libraries via FFI, that promise relies entirely on the quality of the glue code. The `hexchat` crate, a set of Rust bindings for the popular IRC client, contains a fatal flaw in how it manages command lifecycles. By allowing a command callback to capture its own handle and deregister itself during execution, the library enables a classic Use-After-Free scenario. Coupled with thread-safety lies (macros marked safe that aren't), this unmaintained crate serves as a textbook example of how 'Safe Rust' can still segfault if the foundations are rotten.
Rust's primary selling point is its borrow checker—a tyrannical compile-time overlord that prevents you from shooting your own foot. But there is a loophole: unsafe blocks and Foreign Function Interfaces (FFI). When you write bindings for a C application like HexChat, you are effectively negotiating a treaty between the strict, orderly world of Rust and the chaotic, pointer-loving Wild West of C.
The hexchat crate was supposed to be the diplomat. It wraps the raw C API of HexChat into 'safe' Rust abstractions, allowing developers to write plugins without worrying about manual memory management. It promises that if your code compiles, it won't segfault.
It lied. Through a combination of poor lifetime management and improper handling of interior mutability, this crate allows a trivial sequence of safe Rust code to corrupt memory. It’s the software equivalent of a safety harness that unclips itself the moment you put weight on it. To make matters worse, the crate is now abandoned, meaning this vulnerability is effectively a permanent feature for anyone still using it.
The core vulnerability (CWE-416) is a Use-After-Free triggering a logic error in command deregistration. In the HexChat C API, registering a command gives you a handle. To remove that command, you pass the handle back to HexChat.
The Rust bindings wrap this handle in a struct. The flaw arises because the library developers didn't account for reentrancy combined with interior mutability. Specifically, Rust's ownership rules usually prevent a closure from owning the very object that owns the closure (a cycle). However, using RefCell or Rc, a developer can trick the compiler into allowing a command callback to hold a reference to its own command handle.
Here is the kill chain:
/explode)./explode captures a clone of its own handle using Rc<RefCell<...>>./explode, the callback runs.deregister on the captured handle.free()'d, tries to return from the function or access stack variables. Boom.Since there is no patch to diff, let's look at the anti-pattern that triggers this. The vulnerability relies on the interaction between hexchat::hook_command and standard Rust interior mutability patterns.
In a healthy system, the API would prevent a callback from destroying its own execution context. The hexchat crate fails to enforce this isolation. A researcher, SoniEx2, documented this behavior purely in safe Rust:
use std::cell::RefCell;
use std::rc::Rc;
use hexchat::{Plugin, CommandHandle};
// The handle is wrapped in an Option inside a RefCell inside an Rc
// This allows the closure to mutate the handle that owns the closure.
let cmd_handle: Rc<RefCell<Option<CommandHandle>>> = Rc::new(RefCell::new(None));
let cmd_handle_clone = cmd_handle.clone();
*cmd_handle.borrow_mut() = Some(hexchat::hook_command(
"suicide",
move |_| {
// THE SMOKING GUN:
// We borrow the handle mutably inside the callback...
if let Some(handle) = cmd_handle_clone.borrow_mut().take() {
// ...and drop it.
// This calls the Drop trait, which calls hexchat_unhook.
// The C side frees the memory for this hook.
// But we are STILL INSIDE the hook function.
drop(handle);
}
// Any code executed here is walking on freed memory.
hexchat::print("If you see this, you got lucky. Next time, maybe not.");
1 // Return value access might crash
},
hexchat::Priority::Normal,
"Deregisters itself",
));When drop(handle) is called, the underlying C library destroys the hook data structure. However, the closure body (the Rust function) is still on the stack. When the closure attempts to return or access captured variables after the drop, it accesses invalid memory. This is a text-book Use-After-Free, made possible because the abstraction leaked implementation details (the ability to drop the handle) into the context where the handle is active.
As if the UAF wasn't enough, the crate also plays fast and loose with thread safety. Rust has two traits, Send and Sync, which automatically ensure safe concurrency. hexchat bypasses these protections using macros.
The crate exposes macros (likely for printing or command execution) that are marked as safe to call. However, the underlying HexChat C API is single-threaded and generally not thread-safe.
If a plugin spins up a background thread (common for network operations) and calls these macros, it invokes C functions that manipulate global state without locks. This leads to Data Races (CWE-662). In the best case, the IRC client crashes. In the worst case, the race condition corrupts the internal state of the IRC client, potentially leading to memory corruption exploitable for code execution. The fact that these macros were not marked unsafe is a direct violation of Rust's API guidelines.
Exploiting this in the wild is nuanced because it targets the plugin developer (or the user running a malicious plugin), not the IRC protocol directly. However, the vector exists.
An attacker distributes a HexChat plugin promising useful features (e.g., "Auto-Reply Bot").
Since HexChat is an IRC client, RCE here means access to private chat logs, file transfers, and the ability to impersonate the user on IRC networks.
Here is the hard truth: There is no patch. The crate is unmaintained. The architectural flaws (allowing self-referential handle dropping) require a significant rewrite of how the Rust bindings manage ownership of C objects.
cargo audit in your Rust projects. If hexchat appears, you have a problem.unsafe FFI wrappers around hexchat.h.RefCell or Mutex usage inside command callbacks. Ensure no handle is ever moved into the closure it controls.unsafe and wrap them in a mutex that ensures they only run on the main thread.This vulnerability serves as a grim reminder: A library that hasn't been updated in years isn't "stable"—it's rotting.
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:U| Product | Affected Versions | Fixed Version |
|---|---|---|
hexchat crate hexchat-rs developers | >= 0.0.0 | None (Unmaintained) |
| Attribute | Detail |
|---|---|
| Vulnerability ID | GHSA-x43w-ph7m-pfjx |
| CWE | CWE-416 (Use After Free) |
| CVSS v4 | CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H |
| Platform | Rust / HexChat |
| Attack Vector | Local / Network (via IRC trigger) |
| Status | Unmaintained / Unpatched |