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

View File

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

View File

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

View File

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