Summary
nam::get_dsp() aborts the process when handed well-formed JSON that is not a
NAM model. It does not throw, so consumers cannot recover — the whole application
dies.
This is distinct from #314, which was about malformed JSON leaking
nlohmann::json::parse_error. Here the JSON parses fine; it just lacks the keys
Core expects.
It matters for any consumer that accepts files from the user. Ours loads whatever
gets dropped on the window, so a stray .json — a config file, a package.json,
anything — takes the application down with it.
Reproduction
#include <iostream>
#include "NAM/get_dsp.h"
#include "json.hpp"
int main() {
const nlohmann::json j = nlohmann::json::parse(R"({"hello": "world"})");
std::cout << "about to call get_dsp()\n" << std::flush;
try {
auto dsp = nam::get_dsp(j);
std::cout << "loaded (unexpected)\n";
} catch (const std::exception& e) {
std::cout << "threw, as one would hope: " << e.what() << "\n";
}
return 0;
}
Observed on macOS 15 with AppleClang, -O2:
about to call get_dsp()
Assertion failed: (it != m_data.m_value.object->end()), function operator[], file json.hpp, line 22188.
Abort trap: 6
Exit code 134. Adding -DNDEBUG to the get_dsp.cpp translation unit did not
change the outcome in my testing.
Reproduced on v0.5.4; the same unguarded accesses are on main (2563c0f).
Root cause
populate_dsp_data in NAM/get_dsp.cpp indexes three keys with operator[]
without checking that they exist:
verify_config_version(config["version"].get<std::string>());
nlohmann::json config_json = config["config"];
returnedConfig.version = config["version"].get<std::string>();
returnedConfig.architecture = config["architecture"].get<std::string>();
On a const nlohmann::json, operator[] with a missing key asserts, and where
assertions are compiled out it is undefined behaviour. Either way it cannot be
relied on to throw.
weights is handled correctly a few lines up — j.find("weights") followed by a
std::runtime_error — so the fix is to bring the other three in line.
Suggested fix
for (const char* key : {"version", "architecture", "config"}) {
if (!config.contains(key))
throw std::runtime_error(std::string("Corrupted model file is missing ") + key + ".");
}
Or config.at(key), which throws nlohmann::json::out_of_range rather than
asserting — though a std::runtime_error would match the surrounding style and
what consumers already catch.
A type check on version and architecture would help too: {"version": 7, ...}
reaches .get<std::string>() and throws a json::type_error, which is a
different exception type from the std::runtime_error the rest of the loading
path uses.
Workaround for consumers
Validate before calling in:
if (!config.is_object() ||
!config.contains("version") || !config["version"].is_string() ||
!config.contains("architecture") || !config["architecture"].is_string() ||
!config.contains("config")) {
return /* your own load error */;
}
Summary
nam::get_dsp()aborts the process when handed well-formed JSON that is not aNAM model. It does not throw, so consumers cannot recover — the whole application
dies.
This is distinct from #314, which was about malformed JSON leaking
nlohmann::json::parse_error. Here the JSON parses fine; it just lacks the keysCore expects.
It matters for any consumer that accepts files from the user. Ours loads whatever
gets dropped on the window, so a stray
.json— a config file, apackage.json,anything — takes the application down with it.
Reproduction
Observed on macOS 15 with AppleClang,
-O2:Exit code 134. Adding
-DNDEBUGto theget_dsp.cpptranslation unit did notchange the outcome in my testing.
Reproduced on
v0.5.4; the same unguarded accesses are onmain(2563c0f).Root cause
populate_dsp_datainNAM/get_dsp.cppindexes three keys withoperator[]without checking that they exist:
On a
const nlohmann::json,operator[]with a missing key asserts, and whereassertions are compiled out it is undefined behaviour. Either way it cannot be
relied on to throw.
weightsis handled correctly a few lines up —j.find("weights")followed by astd::runtime_error— so the fix is to bring the other three in line.Suggested fix
Or
config.at(key), which throwsnlohmann::json::out_of_rangerather thanasserting — though a
std::runtime_errorwould match the surrounding style andwhat consumers already catch.
A type check on
versionandarchitecturewould help too:{"version": 7, ...}reaches
.get<std::string>()and throws ajson::type_error, which is adifferent exception type from the
std::runtime_errorthe rest of the loadingpath uses.
Workaround for consumers
Validate before calling in: