P1: address review (fail-open hardening)
This commit is contained in:
+52
-18
@@ -13,6 +13,7 @@
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <poll.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#if __APPLE__
|
||||
@@ -75,10 +76,11 @@ static bool allSupportedLocally(Store & store, const std::set<std::string>& requ
|
||||
* (machineHasRoom -> true, liveLoadPenalty -> 0).
|
||||
* ------------------------------------------------------------------------ */
|
||||
|
||||
// Default estimate for a drv that does not match the heavy-crate table.
|
||||
// "Light" means: we have NO confident signal that this drv is memory-heavy,
|
||||
// so machineHasRoom never filters on account of it.
|
||||
static const uint64_t adaptiveLightMib = 512;
|
||||
// A drv that does not match the heavy-crate table is treated as "light":
|
||||
// we have NO confident signal that it is memory-heavy, so adaptiveEstPeakRSS
|
||||
// returns nullopt and machineHasRoom never filters on account of it. (There is
|
||||
// deliberately no numeric light default - an unmatched drv must always permit,
|
||||
// so keying it on a free-RAM threshold would wrongly filter light drvs.)
|
||||
|
||||
// name-substring -> estimated peak RSS in MiB (from LIX_ADAPTIVE_RSS_TABLE).
|
||||
static std::map<std::string, uint64_t> adaptiveRssTable;
|
||||
@@ -108,8 +110,12 @@ static void adaptiveLoadConfig()
|
||||
f >> j;
|
||||
if (j.is_object())
|
||||
for (auto & [k, v] : j.items())
|
||||
if (v.is_number())
|
||||
adaptiveRssTable[k] = v.get<uint64_t>();
|
||||
// Per-entry guard: one bad value skips only that entry,
|
||||
// it does not discard the whole (otherwise valid) table.
|
||||
try {
|
||||
if (v.is_number_unsigned() || (v.is_number_integer() && v.get<int64_t>() >= 0))
|
||||
adaptiveRssTable[k] = v.get<uint64_t>();
|
||||
} catch (...) { continue; }
|
||||
}
|
||||
}
|
||||
} catch (...) { adaptiveRssTable.clear(); }
|
||||
@@ -120,8 +126,11 @@ static void adaptiveLoadConfig()
|
||||
auto j = nlohmann::json::parse(*m);
|
||||
if (j.is_object())
|
||||
for (auto & [k, v] : j.items())
|
||||
if (v.is_string())
|
||||
adaptiveMetricsMap[k] = v.get<std::string>();
|
||||
// Per-entry guard: skip one bad value, keep the rest.
|
||||
try {
|
||||
if (v.is_string())
|
||||
adaptiveMetricsMap[k] = v.get<std::string>();
|
||||
} catch (...) { continue; }
|
||||
}
|
||||
} catch (...) { adaptiveMetricsMap.clear(); }
|
||||
}
|
||||
@@ -143,8 +152,12 @@ static std::optional<uint64_t> adaptiveEstPeakRSS(const StorePath & drvPath)
|
||||
|
||||
/* TCP-connect the metrics endpoint and read one line:
|
||||
* "MemAvail_kB psi_mem psi_io psi_cpu load1 nproc"
|
||||
* Short (500ms) connect+read timeout so selection NEVER hangs. Any failure
|
||||
* returns an AdaptiveProbe with ok=false. */
|
||||
* A single ~500ms wall-clock deadline bounds the WHOLE probe (resolve +
|
||||
* connect + read) so selection NEVER hangs, regardless of a slow or
|
||||
* byte-dribbling peer. The endpoint MUST be a numeric IP:port - resolution is
|
||||
* pinned to AI_NUMERICHOST|AI_NUMERICSERV so getaddrinfo never does network
|
||||
* I/O (a hostname simply fails fast -> fail-open). Any failure returns an
|
||||
* AdaptiveProbe with ok=false. */
|
||||
static AdaptiveProbe adaptiveProbeEndpoint(const std::string & hostport)
|
||||
{
|
||||
AdaptiveProbe r;
|
||||
@@ -154,10 +167,20 @@ static AdaptiveProbe adaptiveProbeEndpoint(const std::string & hostport)
|
||||
std::string host = hostport.substr(0, colon);
|
||||
std::string port = hostport.substr(colon + 1);
|
||||
|
||||
// Single wall-clock budget for the entire probe.
|
||||
auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(500);
|
||||
auto remainingMs = [&]() -> int {
|
||||
auto d = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
deadline - std::chrono::steady_clock::now()).count();
|
||||
return d <= 0 ? 0 : (int) d;
|
||||
};
|
||||
|
||||
struct addrinfo hints;
|
||||
memset(&hints, 0, sizeof hints);
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
// Numeric-only: no DNS, no resolver blocking. Non-IP endpoint -> fail-open.
|
||||
hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
|
||||
struct addrinfo * res = nullptr;
|
||||
if (getaddrinfo(host.c_str(), port.c_str(), &hints, &res) != 0 || !res)
|
||||
return r;
|
||||
@@ -170,7 +193,7 @@ static AdaptiveProbe adaptiveProbeEndpoint(const std::string & hostport)
|
||||
struct pollfd pfd;
|
||||
pfd.fd = fd;
|
||||
pfd.events = POLLOUT;
|
||||
if (poll(&pfd, 1, 500) <= 0) { close(fd); freeaddrinfo(res); return r; }
|
||||
if (poll(&pfd, 1, remainingMs()) <= 0) { close(fd); freeaddrinfo(res); return r; }
|
||||
int soerr = 0;
|
||||
socklen_t sl = sizeof soerr;
|
||||
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &soerr, &sl) < 0 || soerr != 0) {
|
||||
@@ -181,16 +204,27 @@ static AdaptiveProbe adaptiveProbeEndpoint(const std::string & hostport)
|
||||
}
|
||||
freeaddrinfo(res);
|
||||
|
||||
struct timeval tv;
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 500000;
|
||||
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
|
||||
|
||||
/* Read one short line. Keep the socket non-blocking and gate every recv on
|
||||
poll(POLLIN) against the shared deadline, so the total read time is
|
||||
bounded even if the peer drips one byte at a time. A valid reply is tiny,
|
||||
so also cap the number of reads. */
|
||||
std::string line;
|
||||
char buf[512];
|
||||
while (line.size() < 4096) {
|
||||
for (int iter = 0; iter < 16 && line.size() < 4096; ++iter) {
|
||||
int rem = remainingMs();
|
||||
if (rem == 0) break;
|
||||
struct pollfd pfd;
|
||||
pfd.fd = fd;
|
||||
pfd.events = POLLIN;
|
||||
int pr = poll(&pfd, 1, rem);
|
||||
if (pr <= 0) break; // timeout or error -> fail-open
|
||||
if (!(pfd.revents & POLLIN)) break; // POLLHUP/POLLERR with no data
|
||||
ssize_t n = recv(fd, buf, sizeof buf, 0);
|
||||
if (n <= 0) break;
|
||||
if (n < 0) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) continue;
|
||||
break;
|
||||
}
|
||||
if (n == 0) break; // peer closed
|
||||
line.append(buf, n);
|
||||
if (line.find('\n') != std::string::npos) break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user