fixed formatting, that lead to undefined behavior

This commit is contained in:
tdv
2025-12-08 19:28:44 +02:00
parent 21088468b7
commit fe4affd942
4 changed files with 98 additions and 6 deletions

View File

@@ -21,7 +21,9 @@ namespace snoop
auto b = s.find_first_not_of(" \t\r\n"); auto b = s.find_first_not_of(" \t\r\n");
auto e = s.find_last_not_of(" \t\r\n"); auto e = s.find_last_not_of(" \t\r\n");
if (b == std::string::npos) if (b == std::string::npos)
{
return ""; return "";
}
return s.substr(b, e - b + 1); return s.substr(b, e - b + 1);
} }
@@ -31,13 +33,19 @@ namespace snoop
std::string out; std::string out;
FILE *pipe = popen((cmd + " 2>&1").c_str(), "r"); FILE *pipe = popen((cmd + " 2>&1").c_str(), "r");
if (!pipe) if (!pipe)
{
throw std::runtime_error("popen failed: " + cmd); throw std::runtime_error("popen failed: " + cmd);
}
while (fgets(buf.data(), (int)buf.size(), pipe) != nullptr) while (fgets(buf.data(), (int)buf.size(), pipe) != nullptr)
{
out.append(buf.data()); out.append(buf.data());
}
int rc = pclose(pipe); int rc = pclose(pipe);
int exitCode = WIFEXITED(rc) ? WEXITSTATUS(rc) : rc; int exitCode = WIFEXITED(rc) ? WEXITSTATUS(rc) : rc;
if (exitCode != 0) if (exitCode != 0)
{
spdlog::warn("Command '{}' exited with code {}", cmd, exitCode); spdlog::warn("Command '{}' exited with code {}", cmd, exitCode);
}
return out; return out;
} }
@@ -46,12 +54,16 @@ namespace snoop
{ {
std::string id = Trim(Exec("keyctl search @s user iot-client-key | tail -n1")); std::string id = Trim(Exec("keyctl search @s user iot-client-key | tail -n1"));
if (id.empty()) if (id.empty())
{
throw std::runtime_error("iot-client-key not found in keyring"); throw std::runtime_error("iot-client-key not found in keyring");
}
// Create a secure temp file // Create a secure temp file
char tmpl[] = "/run/iot/iot-keyXXXXXX"; char tmpl[] = "/run/iot/iot-keyXXXXXX";
int fd = mkstemp(tmpl); int fd = mkstemp(tmpl);
if (fd < 0) if (fd < 0)
{
throw std::runtime_error("mkstemp failed for client key"); throw std::runtime_error("mkstemp failed for client key");
}
close(fd); close(fd);
std::filesystem::path p(tmpl); std::filesystem::path p(tmpl);
@@ -80,7 +92,9 @@ namespace snoop
name.push_back('\0'); name.push_back('\0');
fd = mkstemp(name.data()); fd = mkstemp(name.data());
if (fd < 0) if (fd < 0)
{
throw std::runtime_error("mkstemp failed"); throw std::runtime_error("mkstemp failed");
}
fchmod(fd, S_IRUSR | S_IWUSR); fchmod(fd, S_IRUSR | S_IWUSR);
path = name.data(); path = name.data();
} }
@@ -92,7 +106,9 @@ namespace snoop
{ {
ssize_t w = ::write(fd, p + off, n - off); ssize_t w = ::write(fd, p + off, n - off);
if (w <= 0) if (w <= 0)
{
throw std::runtime_error("write failed"); throw std::runtime_error("write failed");
}
off += (size_t)w; off += (size_t)w;
} }
fsync(fd); fsync(fd);
@@ -100,7 +116,9 @@ namespace snoop
~TempFile() ~TempFile()
{ {
if (fd >= 0) if (fd >= 0)
{
::close(fd); ::close(fd);
}
std::error_code ec; std::error_code ec;
std::filesystem::remove(path, ec); std::filesystem::remove(path, ec);
} }
@@ -111,12 +129,16 @@ namespace snoop
// 1) get key id // 1) get key id
std::string id = Trim(Exec("keyctl search @s user iot-client-key | tail -n1")); std::string id = Trim(Exec("keyctl search @s user iot-client-key | tail -n1"));
if (id.empty()) if (id.empty())
{
throw std::runtime_error("iot-client-key not found in keyring"); throw std::runtime_error("iot-client-key not found in keyring");
}
// 2) capture payload (no redirection to file) // 2) capture payload (no redirection to file)
std::string bytes = Exec("keyctl pipe " + id); std::string bytes = Exec("keyctl pipe " + id);
if (bytes.empty()) if (bytes.empty())
{
throw std::runtime_error("keyctl pipe returned empty payload"); throw std::runtime_error("keyctl pipe returned empty payload");
}
return std::vector<uint8_t>(bytes.begin(), bytes.end()); return std::vector<uint8_t>(bytes.begin(), bytes.end());
} }

View File

@@ -207,7 +207,9 @@ namespace snoop
// Setup CA/CRT (from enrollment) // Setup CA/CRT (from enrollment)
std::filesystem::path ca = "/etc/iot/keys/issuing_ca.pem"; std::filesystem::path ca = "/etc/iot/keys/issuing_ca.pem";
if (!std::filesystem::exists(ca)) if (!std::filesystem::exists(ca))
{
ca = "/etc/iot/keys/ca_chain.pem"; ca = "/etc/iot/keys/ca_chain.pem";
}
const std::filesystem::path crt = "/etc/iot/keys/device.crt.pem"; const std::filesystem::path crt = "/etc/iot/keys/device.crt.pem";
if (!needReinit) if (!needReinit)
@@ -337,7 +339,9 @@ namespace snoop
// certs from enrollment // certs from enrollment
std::filesystem::path ca = "/etc/iot/keys/issuing_ca.pem"; std::filesystem::path ca = "/etc/iot/keys/issuing_ca.pem";
if (!std::filesystem::exists(ca)) if (!std::filesystem::exists(ca))
{
ca = "/etc/iot/keys/ca_chain.pem"; ca = "/etc/iot/keys/ca_chain.pem";
}
const std::filesystem::path crt = "/etc/iot/keys/device.crt.pem"; const std::filesystem::path crt = "/etc/iot/keys/device.crt.pem";
while (!m_isIntermission) while (!m_isIntermission)

View File

@@ -36,7 +36,9 @@ namespace snoop
std::string out; std::string out;
FILE *pipe = popen((cmd + " 2>&1").c_str(), "r"); FILE *pipe = popen((cmd + " 2>&1").c_str(), "r");
if (!pipe) if (!pipe)
{
throw std::runtime_error("popen failed: " + cmd); throw std::runtime_error("popen failed: " + cmd);
}
while (fgets(buf.data(), (int)buf.size(), pipe) != nullptr) while (fgets(buf.data(), (int)buf.size(), pipe) != nullptr)
{ {
out.append(buf.data()); out.append(buf.data());
@@ -44,17 +46,23 @@ namespace snoop
auto rc = pclose(pipe); auto rc = pclose(pipe);
int exitCode = WIFEXITED(rc) ? WEXITSTATUS(rc) : rc; int exitCode = WIFEXITED(rc) ? WEXITSTATUS(rc) : rc;
if (exitCode != 0) if (exitCode != 0)
{
spdlog::warn("Command '{}' exited with code {}", cmd, exitCode); spdlog::warn("Command '{}' exited with code {}", cmd, exitCode);
}
return out; return out;
} }
static void WriteFile(const std::filesystem::path &p, const std::string &data, bool createParents = true) static void WriteFile(const std::filesystem::path &p, const std::string &data, bool createParents = true)
{ {
if (createParents) if (createParents)
{
std::filesystem::create_directories(p.parent_path()); std::filesystem::create_directories(p.parent_path());
}
std::ofstream f(p, std::ios::binary); std::ofstream f(p, std::ios::binary);
if (!f) if (!f)
{
throw std::runtime_error("Cannot open file for write: " + p.string()); throw std::runtime_error("Cannot open file for write: " + p.string());
}
f.write(data.data(), (std::streamsize)data.size()); f.write(data.data(), (std::streamsize)data.size());
} }
@@ -62,7 +70,9 @@ namespace snoop
{ {
std::ifstream f(p, std::ios::binary); std::ifstream f(p, std::ios::binary);
if (!f) if (!f)
{
throw std::runtime_error("Cannot open file for read: " + p.string()); throw std::runtime_error("Cannot open file for read: " + p.string());
}
std::ostringstream ss; std::ostringstream ss;
ss << f.rdbuf(); ss << f.rdbuf();
return ss.str(); return ss.str();
@@ -97,9 +107,11 @@ namespace snoop
{ {
auto serial = Trim(m[1].str()); auto serial = Trim(m[1].str());
if (!serial.empty()) if (!serial.empty())
{
return serial; return serial;
} }
} }
}
spdlog::warn("CPU Serial not found, using fallback"); spdlog::warn("CPU Serial not found, using fallback");
return fallback; return fallback;
} }
@@ -221,7 +233,9 @@ namespace snoop
std::filesystem::path(csrName)); std::filesystem::path(csrName));
if (!std::filesystem::exists(keyName) || !std::filesystem::exists(csrName)) if (!std::filesystem::exists(keyName) || !std::filesystem::exists(csrName))
{
throw std::runtime_error("CSR or key was not generated"); throw std::runtime_error("CSR or key was not generated");
}
std::filesystem::create_directories(keystoreDir); std::filesystem::create_directories(keystoreDir);
@@ -232,8 +246,9 @@ namespace snoop
kek); kek);
if (!std::filesystem::exists(encKeyPath)) if (!std::filesystem::exists(encKeyPath))
{
throw std::runtime_error("Encrypted key not created: " + encKeyPath.string()); throw std::runtime_error("Encrypted key not created: " + encKeyPath.string());
}
// shred plaintext key // shred plaintext key
const std::string shredCmd = "shred -u " + keyName; const std::string shredCmd = "shred -u " + keyName;
spdlog::info("Shredding plaintext private key with '{}'", shredCmd); spdlog::info("Shredding plaintext private key with '{}'", shredCmd);
@@ -259,8 +274,9 @@ namespace snoop
std::filesystem::path(csrName)); std::filesystem::path(csrName));
if (!std::filesystem::exists(csrName)) if (!std::filesystem::exists(csrName))
{
throw std::runtime_error("CSR was not generated from existing key"); throw std::runtime_error("CSR was not generated from existing key");
}
// TempFile destructor will securely delete the decrypted key file // TempFile destructor will securely delete the decrypted key file
} }
@@ -320,10 +336,15 @@ namespace snoop
spdlog::info("POST {} (multipart)", path); spdlog::info("POST {} (multipart)", path);
auto res = cli.Post(path.c_str(), items); auto res = cli.Post(path.c_str(), items);
if (!res) if (!res)
{
throw std::runtime_error("Enroll request failed (no response)"); throw std::runtime_error("Enroll request failed (no response)");
}
spdlog::info("Enroll response status: {}", res->status); spdlog::info("Enroll response status: {}", res->status);
if (res->status != 200 && res->status != 201) if (res->status != 200 && res->status != 201)
{
spdlog::info("Enroll error: " + res->body);
throw std::runtime_error("Enroll failed: HTTP " + std::to_string(res->status)); throw std::runtime_error("Enroll failed: HTTP " + std::to_string(res->status));
}
// 6) Expect JSON: { "certificate": "...", "issuing_ca":"...", "ca_chain":"..." } // 6) Expect JSON: { "certificate": "...", "issuing_ca":"...", "ca_chain":"..." }
nlohmann::json j; nlohmann::json j;
@@ -336,12 +357,16 @@ namespace snoop
throw std::runtime_error(std::string("Enroll: invalid JSON: ") + e.what()); throw std::runtime_error(std::string("Enroll: invalid JSON: ") + e.what());
} }
if (!j.contains("certificate") || !j["certificate"].is_string()) if (!j.contains("certificate") || !j["certificate"].is_string())
{
throw std::runtime_error("Enroll response missing or invalid 'certificate'"); throw std::runtime_error("Enroll response missing or invalid 'certificate'");
}
WriteFile(certPath, j["certificate"].get<std::string>()); WriteFile(certPath, j["certificate"].get<std::string>());
if (j.contains("issuing_ca")) if (j.contains("issuing_ca"))
{ {
if (!j["issuing_ca"].is_string()) if (!j["issuing_ca"].is_string())
{
throw std::runtime_error("'issuing_ca' must be a string PEM"); throw std::runtime_error("'issuing_ca' must be a string PEM");
}
WriteFile(caPath, j["issuing_ca"].get<std::string>()); WriteFile(caPath, j["issuing_ca"].get<std::string>());
} }
@@ -455,15 +480,21 @@ namespace snoop
auto res = cli->Post(path.c_str(), items); auto res = cli->Post(path.c_str(), items);
if (!res) if (!res)
{
throw std::runtime_error("Renew request failed (no response)"); throw std::runtime_error("Renew request failed (no response)");
}
spdlog::info("Renew response status: {}", res->status); spdlog::info("Renew response status: {}", res->status);
if (res->status != 200) if (res->status != 200)
{
throw std::runtime_error("Renew failed: HTTP " + std::to_string(res->status)); throw std::runtime_error("Renew failed: HTTP " + std::to_string(res->status));
}
nlohmann::json j = nlohmann::json::parse(res->body); nlohmann::json j = nlohmann::json::parse(res->body);
if (!j.contains("certificate") || !j["certificate"].is_string()) if (!j.contains("certificate") || !j["certificate"].is_string())
{
throw std::runtime_error("Renew response missing or invalid 'certificate'"); throw std::runtime_error("Renew response missing or invalid 'certificate'");
}
WriteFile(certPath, j["certificate"].get<std::string>()); WriteFile(certPath, j["certificate"].get<std::string>());
if (j.contains("issuing_ca") && j["issuing_ca"].is_string()) if (j.contains("issuing_ca") && j["issuing_ca"].is_string())
@@ -475,11 +506,17 @@ namespace snoop
{ {
std::string chainPem; std::string chainPem;
if (j["ca_chain"].is_string()) if (j["ca_chain"].is_string())
{
chainPem = j["ca_chain"].get<std::string>(); chainPem = j["ca_chain"].get<std::string>();
}
else if (j["ca_chain"].is_array()) else if (j["ca_chain"].is_array())
{
chainPem = JoinPemArray(j["ca_chain"]); chainPem = JoinPemArray(j["ca_chain"]);
}
else else
{
throw std::runtime_error("'ca_chain' must be string or array"); throw std::runtime_error("'ca_chain' must be string or array");
}
WriteFile(chainPath, chainPem); WriteFile(chainPath, chainPem);
} }
@@ -525,7 +562,9 @@ namespace snoop
{ {
std::ifstream f(path, std::ios::binary); std::ifstream f(path, std::ios::binary);
if (!f) if (!f)
{
throw std::runtime_error("Failed to open file: " + path); throw std::runtime_error("Failed to open file: " + path);
}
std::ostringstream ss; std::ostringstream ss;
ss << f.rdbuf(); ss << f.rdbuf();
return ss.str(); return ss.str();

View File

@@ -49,7 +49,9 @@ namespace snoop
{ {
std::lock_guard lk(m_mtx); std::lock_guard lk(m_mtx);
if (m_started) if (m_started)
{
return; return;
}
// Parse & normalize the provided URL now (extract token, add trailing /whip) // Parse & normalize the provided URL now (extract token, add trailing /whip)
m_endpoint = ParseWhipUrl(m_p.whipUrl); m_endpoint = ParseWhipUrl(m_p.whipUrl);
@@ -101,7 +103,8 @@ namespace snoop
m_pc->onGatheringStateChange([this](rtc::PeerConnection::GatheringState s) m_pc->onGatheringStateChange([this](rtc::PeerConnection::GatheringState s)
{ {
spdlog::info("WHIP gathering state: {}", (int)s); spdlog::info("WHIP gathering state: {}", (int)s);
if (s == rtc::PeerConnection::GatheringState::Complete) { if (s == rtc::PeerConnection::GatheringState::Complete)
{
PatchSdpFrag("a=end-of-candidates"); PatchSdpFrag("a=end-of-candidates");
} }); } });
@@ -136,7 +139,9 @@ namespace snoop
{ {
std::lock_guard lk(m_mtx); std::lock_guard lk(m_mtx);
if (!m_started) if (!m_started)
{
return; return;
}
m_trackOpen = false; m_trackOpen = false;
m_track.reset(); m_track.reset();
@@ -150,7 +155,9 @@ namespace snoop
{ {
std::lock_guard lk(m_mtx); std::lock_guard lk(m_mtx);
if (!m_track || !m_started || !m_trackOpen.load()) if (!m_track || !m_started || !m_trackOpen.load())
{
return; return;
}
std::array<uint8_t, 12> rtp{}; std::array<uint8_t, 12> rtp{};
rtp[0] = 0x80; // V=2 rtp[0] = 0x80; // V=2
@@ -233,7 +240,9 @@ namespace snoop
{ {
size_t amp = query.find('&', pos); size_t amp = query.find('&', pos);
if (amp == std::string::npos) if (amp == std::string::npos)
{
amp = query.size(); amp = query.size();
}
auto kv = query.substr(pos, amp - pos); auto kv = query.substr(pos, amp - pos);
auto eq = kv.find('='); auto eq = kv.find('=');
if (eq != std::string::npos) if (eq != std::string::npos)
@@ -252,9 +261,13 @@ namespace snoop
// MediaMTX wants: /whip/<original...>/whip // MediaMTX wants: /whip/<original...>/whip
// Your incoming URLs already start with /whip/... — we just ensure they end with /whip. // Your incoming URLs already start with /whip/... — we just ensure they end with /whip.
if (rawPath.empty()) if (rawPath.empty())
{
rawPath = "/"; rawPath = "/";
}
if (rawPath.back() == '/') if (rawPath.back() == '/')
{
rawPath.pop_back(); rawPath.pop_back();
}
if (rawPath.rfind("/whip", std::string::npos) != rawPath.size() - 5) if (rawPath.rfind("/whip", std::string::npos) != rawPath.size() - 5)
{ {
rawPath += "/whip"; rawPath += "/whip";
@@ -266,7 +279,9 @@ namespace snoop
static std::tuple<std::string, std::string> ExtractAnswerAndLocation(const httplib::Result &r) static std::tuple<std::string, std::string> ExtractAnswerAndLocation(const httplib::Result &r)
{ {
if (!r) if (!r)
{
throw std::runtime_error("No HTTP result"); throw std::runtime_error("No HTTP result");
}
if (r->status != 201 && r->status != 200) if (r->status != 201 && r->status != 200)
{ {
throw std::runtime_error("Unexpected WHIP status: " + std::to_string(r->status)); throw std::runtime_error("Unexpected WHIP status: " + std::to_string(r->status));
@@ -274,7 +289,9 @@ namespace snoop
std::string answer = r->body; std::string answer = r->body;
std::string resourceUrl; std::string resourceUrl;
if (r->has_header("Location")) if (r->has_header("Location"))
{
resourceUrl = r->get_header_value("Location"); resourceUrl = r->get_header_value("Location");
}
return {answer, resourceUrl}; return {answer, resourceUrl};
} }
@@ -294,7 +311,9 @@ namespace snoop
auto res = cli->Post(m_endpoint.path.c_str(), hs, sdpOffer, "application/sdp"); auto res = cli->Post(m_endpoint.path.c_str(), hs, sdpOffer, "application/sdp");
if (!res) if (!res)
{
throw std::runtime_error("No HTTP result (network?)"); throw std::runtime_error("No HTTP result (network?)");
}
const auto ctype = res->get_header_value("Content-Type"); const auto ctype = res->get_header_value("Content-Type");
const bool has_loc = res->has_header("Location"); const bool has_loc = res->has_header("Location");
@@ -313,7 +332,9 @@ namespace snoop
std::string answer = res->body; std::string answer = res->body;
std::string resourceUrl; std::string resourceUrl;
if (res->has_header("Location")) if (res->has_header("Location"))
{
resourceUrl = res->get_header_value("Location"); resourceUrl = res->get_header_value("Location");
}
if (answer.find("a=ice-ufrag:") == std::string::npos) if (answer.find("a=ice-ufrag:") == std::string::npos)
{ {
@@ -334,7 +355,9 @@ namespace snoop
// If MediaMTX returns an absolute Location for the resource, we parse it; // If MediaMTX returns an absolute Location for the resource, we parse it;
// otherwise we reuse the same host/port and use Location as path. // otherwise we reuse the same host/port and use Location as path.
if (!m_resourceUrl) if (!m_resourceUrl)
{
return; return;
}
ParsedUrl target; ParsedUrl target;
try try
@@ -393,7 +416,9 @@ namespace snoop
void PatchSdpFrag(const std::string &sdpfrag) void PatchSdpFrag(const std::string &sdpfrag)
{ {
if (!m_resourceUrl) if (!m_resourceUrl)
{
return; return;
}
// Reuse the same host/port and just set path = Location // Reuse the same host/port and just set path = Location
ParsedUrl target = m_endpoint; ParsedUrl target = m_endpoint;
@@ -411,7 +436,9 @@ namespace snoop
// MUST include CRLF at end of body // MUST include CRLF at end of body
std::string body = sdpfrag; std::string body = sdpfrag;
if (body.empty() || body.back() != '\n') if (body.empty() || body.back() != '\n')
{
body += "\r\n"; body += "\r\n";
}
auto res = cli->Patch(target.path.c_str(), hs, body, "application/trickle-ice-sdpfrag"); auto res = cli->Patch(target.path.c_str(), hs, body, "application/trickle-ice-sdpfrag");
if (!res || !(res->status == 200 || res->status == 201 || res->status == 204)) if (!res || !(res->status == 200 || res->status == 201 || res->status == 204))