// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Variant repro for bug 501376612 (commit 174583c9fcf83). The parent fix
// added WeakPtr guards to several authenticators in remoting/protocol/ but
// did not patch remoting/host/pam_authorization_factory_posix.cc, which
// contains the verbatim same `base::Unretained(this)` pattern with the
// verbatim same (incorrect) safety comment.

#include "remoting/host/pam_authorization_factory_posix.h"

#include <memory>
#include <string>
#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/test/task_environment.h"
#include "remoting/protocol/authenticator.h"
#include "remoting/protocol/channel_authenticator.h"
#include "remoting/signaling/jingle_data_structures.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace remoting {

namespace {

// A test Authenticator whose GetNextMessage() runs a caller-supplied
// trigger before continuing. The trigger is invoked synchronously to mimic
// what happens in production when a real underlying authenticator
// transitions to ACCEPTED and fires the state-change-after-accepted
// callback chain — in production that chain ends in connection-teardown
// code that destroys the wrapping authenticator while we are still on its
// stack.
class TestUnderlyingAuthenticator : public protocol::Authenticator {
 public:
  TestUnderlyingAuthenticator() = default;
  ~TestUnderlyingAuthenticator() override = default;

  void set_get_next_message_trigger(base::OnceClosure trigger) {
    trigger_ = std::move(trigger);
  }

  // protocol::Authenticator:
  protocol::CredentialsType credentials_type() const override {
    return protocol::CredentialsType::SHARED_SECRET;
  }
  const Authenticator& implementing_authenticator() const override {
    return *this;
  }
  State state() const override { return state_; }
  bool started() const override { return true; }
  RejectionReason rejection_reason() const override {
    return RejectionReason::INVALID_CREDENTIALS;
  }
  RejectionDetails rejection_details() const override {
    return RejectionDetails(std::string_view("test"));
  }
  void ProcessMessage(const JingleAuthentication& message,
                      base::OnceClosure resume_callback) override {
    std::move(resume_callback).Run();
  }
  JingleAuthentication GetNextMessage() override {
    if (trigger_) {
      // Run the trigger BEFORE we return. After we return, control flows
      // back to PamAuthorizer::GetNextMessage at line 117, which then
      // dereferences `this` at line 119 (`MaybeCheckLocalLogin();`).
      // Note that the trigger destroys the wrapping PamAuthorizer, which
      // also destroys this TestUnderlyingAuthenticator (since it is held
      // by PamAuthorizer's `underlying_` unique_ptr). The first
      // dereference of freed memory therefore actually happens slightly
      // before line 119 — when this function returns and stack unwind
      // touches `this`. ASAN catches it either way; the freed-by stack
      // will point to ~PamAuthorizer, demonstrating the bug class.
      std::move(trigger_).Run();
    }
    return JingleAuthentication();
  }
  const std::string& GetAuthKey() const override {
    static const std::string empty;
    return empty;
  }
  const SessionPolicies* GetSessionPolicies() const override {
    return nullptr;
  }
  std::unique_ptr<protocol::ChannelAuthenticator> CreateChannelAuthenticator()
      const override {
    return nullptr;
  }

 private:
  State state_ = MESSAGE_READY;
  base::OnceClosure trigger_;
};

// A minimal AuthenticatorFactory that hands out a single
// pre-constructed TestUnderlyingAuthenticator. This lets us drive a
// PamAuthorizer through the public PamAuthorizationFactory entry point
// (PamAuthorizer is in an anonymous namespace inside
// pam_authorization_factory_posix.cc and cannot be constructed directly).
class StubAuthenticatorFactory : public protocol::AuthenticatorFactory {
 public:
  explicit StubAuthenticatorFactory(
      std::unique_ptr<TestUnderlyingAuthenticator> auth)
      : auth_(std::move(auth)) {}
  ~StubAuthenticatorFactory() override = default;

  std::unique_ptr<protocol::Authenticator> CreateAuthenticator(
      const std::string& local_jid,
      const std::string& remote_jid) override {
    return std::move(auth_);
  }

  std::unique_ptr<protocol::AuthenticatorFactory> Clone() const override {
    return std::make_unique<StubAuthenticatorFactory>(
        std::make_unique<TestUnderlyingAuthenticator>());
  }

 private:
  std::unique_ptr<TestUnderlyingAuthenticator> auth_;
};

}  // namespace

class PamAuthorizerUafTest : public testing::Test {
 public:
  base::test::SingleThreadTaskEnvironment task_environment_;
};

// Reproduces the variant of bug 501376612 in
// remoting/host/pam_authorization_factory_posix.cc.
//
// Setup:
//   1. PamAuthorizer (constructed via PamAuthorizationFactory) wraps a
//      TestUnderlyingAuthenticator.
//   2. We hold the PamAuthorizer in a std::unique_ptr.
//   3. The test underlying's GetNextMessage() runs a trigger that drops
//      the std::unique_ptr, synchronously freeing the PamAuthorizer (and
//      its underlying_, which is currently mid-call).
//   4. After underlying_->GetNextMessage() returns, control flows back to
//      PamAuthorizer::GetNextMessage line 119, which calls
//      MaybeCheckLocalLogin() on the freed `this`.
//
// Without the variant fix: ASAN heap-use-after-free.
// With the fix (parallel to commit 174583c9fcf83): test passes cleanly.
TEST_F(PamAuthorizerUafTest, GetNextMessage_FreesThisDuringUnderlyingCall) {
  auto underlying = std::make_unique<TestUnderlyingAuthenticator>();
  TestUnderlyingAuthenticator* underlying_raw = underlying.get();

  auto stub_factory =
      std::make_unique<StubAuthenticatorFactory>(std::move(underlying));
  PamAuthorizationFactory pam_factory(std::move(stub_factory));

  std::unique_ptr<protocol::Authenticator> pam =
      pam_factory.CreateAuthenticator("local@jid", "remote@jid");
  ASSERT_NE(pam, nullptr);

  underlying_raw->set_get_next_message_trigger(base::BindOnce(
      [](std::unique_ptr<protocol::Authenticator>* slot) { slot->reset(); },
      &pam));

  // Capture raw before the trigger destroys the unique_ptr.
  protocol::Authenticator* pam_raw = pam.get();

  // Trigger the bug. ASAN should fire here.
  std::ignore = pam_raw->GetNextMessage();

  // Reaching this line means the bug did not fire.
  EXPECT_EQ(pam, nullptr);
}

}  // namespace remoting
