libstore: fix AddBuildLog denial of service

adding logs to a daemon store can fail before the entire request has
been read, leaving non-command data in socket buffers. if the daemon
does not terminate immediately after the failure it will try to read
the remaining data as commands, which is very likely to not work and
cause unbounded memory allocation instead. this doesn't give clients
an attack vector they did not have before though, they could've just
as well sent malformed commands without a bad preceding AddBuildLog.

Change-Id: I86c22890eace19164d932cbd342ac9f52cee6531
This commit is contained in:
eldritch horrors
2026-07-17 16:48:28 +00:00
parent 33d0984e27
commit 553ae61ffd
2 changed files with 50 additions and 2 deletions
+3 -2
View File
@@ -845,11 +845,12 @@ static void performOp(AsyncIoRoot & aio, TunnelLogger * logger, ref<Store> store
case WorkerProto::Op::AddBuildLog: {
StorePath path{readString(from)};
logger->startWork();
if (!trusted)
throw Error("you are not privileged to add logs");
auto & logStore = require<LogStore>(*store);
{
FramedSource source(from);
if (!trusted) {
throw Error("you are not privileged to add logs");
}
StringSink sink;
source.drainInto(sink);
aio.blockOn(logStore.addBuildLog(path, sink.s));
+47
View File
@@ -0,0 +1,47 @@
from pathlib import Path
import shutil
import pytest
from testlib.fixtures.file_helper import with_files
from testlib.fixtures.nix import Nix, NixDaemon
from testlib.utils import get_global_asset_pack
@with_files({"simple": get_global_asset_pack("simple-drv")})
class TestCopyLog:
@pytest.fixture(autouse=True)
def setup(self, nix: Nix, files: Path):
nix.settings.add_xp_feature("nix-command")
# build test drv in a second store so we actually have a log to copy
nix.nix(["build", "-f", f"{files}/simple/simple.nix"]).run().ok()
self.drv = nix.nix_store(["--query", "--deriver", "result"]).run().ok().stdout_plain
self.log_text = nix.nix(["log", self.drv]).run().ok().stdout_s
@pytest.mark.no_daemon
def test_copy_log_to_cache(self, nix: Nix):
nix.nix(
["store", "copy-log", "--to", f"file://{nix.env.dirs.cache_dir}", self.drv]
).run().ok()
shutil.rmtree(nix.env.dirs.nix_log_dir)
nix.nix(["log", self.drv]).run().expect(1)
nix.nix(
["store", "copy-log", "--from", f"file://{nix.env.dirs.cache_dir}", self.drv]
).run().ok()
assert self.log_text == nix.nix(["log", self.drv]).run().ok().stdout_s
def test_copy_log_untrusted(self, nix: Nix, daemon: NixDaemon):
nix.nix(["store", "copy-log", "--to", nix.env.dirs.cache_dir, self.drv]).run().ok()
with daemon(nix) as inner:
cmd = inner.nix(["store", "copy-log", "--from", nix.env.dirs.cache_dir, self.drv]).run()
cmd.expect(1)
assert "you are not privileged to add logs" in cmd.stderr_plain
@pytest.mark.nix_settings(trusted_users="*")
def test_copy_log_trusted(self, nix: Nix):
nix.nix(["store", "copy-log", "--to", nix.env.dirs.cache_dir, self.drv]).run().ok()
shutil.rmtree(nix.env.dirs.nix_log_dir)
nix.nix(["log", self.drv]).run().expect(1)
nix.nix(["store", "copy-log", "--from", nix.env.dirs.cache_dir, self.drv]).run().ok()
assert self.log_text == nix.nix(["log", self.drv]).run().ok().stdout_s