0

Reland "[base] Prevent access to feature state before FeatureList registration."

This is a reland of commit 033319ab36

This should be safe to land now that this other CL
https://crrev.com/c/4002288 fixed an incorrect early feature access.

As an extra precaution, the CHECK was changed to a DCHECK:
https://crrev.com/c/4006674/1..2/base/feature_list.cc
A DumpWithoutCrashing was added to get error reports from the field
without making Chrome unusable.

Original change's description:
> [base] Prevent access to feature state before FeatureList registration.
>
> With this CL, we CHECK that no feature state was accessed previously
> when registering a FeatureList. This is to prevent accessing feature
> state too early in processes where we expect to find a FeatureList,
> while tolerating accesses in processes that never create a
> FeatureList.
>
> To keep this CL small, the CHECK is only enabled on Windows, Mac and
> Linux for now. Enabling this on Android, iOS and ChromeOS requires
> fixing many more accesses throughout the codebase.
>
> Bug: 1358639
> Change-Id: I2cbf3e2e1ac46ca0a14679fd438ecf0a774f9148
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3867967
> Reviewed-by: Kentaro Hara <haraken@chromium.org>
> Reviewed-by: Jian Li <jianli@chromium.org>
> Reviewed-by: Olga Sharonova <olka@chromium.org>
> Reviewed-by: Charlie Reis <creis@chromium.org>
> Commit-Queue: Francois Pierre Doray <fdoray@chromium.org>
> Reviewed-by: Alexei Svitkine <asvitkine@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#1066783}

Bug: 1358639
Change-Id: I9932f25272e95b2ae132ba19e76b6a6365e28a17
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4006674
Reviewed-by: Charlie Reis <creis@chromium.org>
Reviewed-by: Olga Sharonova <olka@chromium.org>
Reviewed-by: Jian Li <jianli@chromium.org>
Reviewed-by: Kentaro Hara <haraken@chromium.org>
Commit-Queue: Francois Pierre Doray <fdoray@chromium.org>
Reviewed-by: Alexei Svitkine <asvitkine@chromium.org>
Auto-Submit: Francois Pierre Doray <fdoray@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1070450}
This commit is contained in:
François Doray
2022-11-11 20:11:57 +00:00
committed by Chromium LUCI CQ
parent 681f05439b
commit 12292a5967
7 changed files with 106 additions and 40 deletions
base
components/offline_pages/core/prefetch/tasks
content/public/test
gin/shell
third_party/blink/renderer/core/scroll

@ -9,10 +9,9 @@
#include <stddef.h>
#include "base/base_paths.h"
#include "base/base_switches.h"
#include "base/containers/contains.h"
#include "base/debug/alias.h"
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/debug/stack_trace.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
@ -20,8 +19,8 @@
#include "base/metrics/field_trial_param_associator.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/persistent_memory_allocator.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/path_service.h"
#include "base/pickle.h"
#include "base/rand_util.h"
#include "base/strings/string_piece.h"
@ -40,9 +39,66 @@ namespace {
// have more control over initialization timing. Leaky.
FeatureList* g_feature_list_instance = nullptr;
// Tracks whether the FeatureList instance was initialized via an accessor, and
// which Feature that accessor was for, if so.
const Feature* g_initialized_from_accessor = nullptr;
// Tracks access to Feature state before FeatureList registration.
class EarlyFeatureAccessTracker {
public:
static EarlyFeatureAccessTracker* GetInstance() {
static NoDestructor<EarlyFeatureAccessTracker> instance;
return instance.get();
}
// Invoked when `feature` is accessed before FeatureList registration.
void AccessedFeature(const Feature& feature) {
AutoLock lock(lock_);
if (feature_)
return;
feature_ = &feature;
stack_trace_ = std::make_unique<debug::StackTrace>();
}
// Asserts that no feature was accessed before FeatureList registration.
void AssertNoAccess() {
AutoLock lock(lock_);
if (!feature_)
return;
#if !BUILDFLAG(IS_NACL)
// Create a crash key with the name of the feature accessed too early, to
// facilitate crash triage.
SCOPED_CRASH_KEY_STRING256("FeatureList", "feature-accessed-too-early",
feature_->name);
#endif // !BUILDFLAG(IS_NACL)
// Fail if DCHECKs are enabled.
DCHECK(!feature_) << "Accessed feature " << feature_->name
<< " before FeatureList registration, in stack: "
<< stack_trace_->ToString();
// Report the problem but don't crash if DCHECKs are disabled, to avoid
// making Chrome unusable if a feature is accessed too early.
base::debug::DumpWithoutCrashing();
}
// Resets the state of this tracker.
void Reset() {
AutoLock lock(lock_);
feature_ = nullptr;
stack_trace_.reset();
}
private:
friend class NoDestructor<EarlyFeatureAccessTracker>;
EarlyFeatureAccessTracker() = default;
~EarlyFeatureAccessTracker() = default;
Lock lock_;
// First feature to be accessed before FeatureList registration.
const Feature* feature_ GUARDED_BY(lock_) = nullptr;
// Stack trace of the first feature access before FeatureList registration.
std::unique_ptr<debug::StackTrace> stack_trace_ GUARDED_BY(lock_);
};
// Controls whether a feature's override state will be cached in
// `base::Feature::cached_value`. This field and the associated `base::Feature`
@ -385,7 +441,7 @@ bool FeatureList::IsEnabled(const Feature& feature) {
CHECK(g_use_allowed) << "base::Feature not permitted for this module.";
#endif
if (!g_feature_list_instance) {
g_initialized_from_accessor = &feature;
EarlyFeatureAccessTracker::GetInstance()->AccessedFeature(feature);
return feature.default_state == FEATURE_ENABLED_BY_DEFAULT;
}
return g_feature_list_instance->IsFeatureEnabled(feature);
@ -402,7 +458,7 @@ absl::optional<bool> FeatureList::GetStateIfOverridden(const Feature& feature) {
CHECK(g_use_allowed) << "base::Feature not permitted for this module.";
#endif
if (!g_feature_list_instance) {
g_initialized_from_accessor = &feature;
EarlyFeatureAccessTracker::GetInstance()->AccessedFeature(feature);
// If there is no feature list, there can be no overrides.
return absl::nullopt;
}
@ -416,7 +472,7 @@ FieldTrial* FeatureList::GetFieldTrial(const Feature& feature) {
CHECK(g_use_allowed) << "base::Feature not permitted for this module.";
#endif
if (!g_feature_list_instance) {
g_initialized_from_accessor = &feature;
EarlyFeatureAccessTracker::GetInstance()->AccessedFeature(feature);
return nullptr;
}
return g_feature_list_instance->GetAssociatedFieldTrial(feature);
@ -490,10 +546,7 @@ bool FeatureList::InitializeInstance(
// If the singleton was previously initialized from within an accessor, we
// want to prevent callers from reinitializing the singleton and masking the
// accessor call(s) which likely returned incorrect information.
if (g_initialized_from_accessor) {
DEBUG_ALIAS_FOR_CSTR(accessor_name, g_initialized_from_accessor->name, 128);
CHECK(!g_initialized_from_accessor);
}
EarlyFeatureAccessTracker::GetInstance()->AssertNoAccess();
bool instance_existed_before = false;
if (g_feature_list_instance) {
if (g_feature_list_instance->initialized_from_command_line_)
@ -524,10 +577,16 @@ void FeatureList::SetInstance(std::unique_ptr<FeatureList> instance) {
// Note: Intentional leak of global singleton.
g_feature_list_instance = instance.release();
// TODO(crbug.com/1358639): Enable this check on all platforms.
#if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
EarlyFeatureAccessTracker::GetInstance()->AssertNoAccess();
#endif
#if !BUILDFLAG(IS_NACL)
// Configured first because it takes precedence over the getrandom() trial.
internal::ConfigureBoringSSLBackedRandBytesFieldTrial();
#endif
#if BUILDFLAG(IS_ANDROID)
internal::ConfigureRandBytesFieldTrial();
#endif
@ -559,7 +618,7 @@ void FeatureList::SetInstance(std::unique_ptr<FeatureList> instance) {
std::unique_ptr<FeatureList> FeatureList::ClearInstanceForTesting() {
FeatureList* old_instance = g_feature_list_instance;
g_feature_list_instance = nullptr;
g_initialized_from_accessor = nullptr;
EarlyFeatureAccessTracker::GetInstance()->Reset();
return WrapUnique(old_instance);
}
@ -575,7 +634,7 @@ void FeatureList::RestoreInstanceForTesting(
void FeatureList::ForbidUseForCurrentModule() {
#if DCHECK_IS_ON()
// Verify there hasn't been any use prior to being called.
DCHECK(!g_initialized_from_accessor);
EarlyFeatureAccessTracker::GetInstance()->AssertNoAccess();
g_use_allowed = false;
#endif // DCHECK_IS_ON()
}

@ -378,10 +378,17 @@ class BASE_EXPORT FeatureList {
// OWNERS.
std::unique_ptr<Accessor> ConstructAccessor();
// Returns whether the given |feature| is enabled. Must only be called after
// the singleton instance has been registered via SetInstance(). Additionally,
// a feature with a given name must only have a single corresponding Feature
// struct, which is checked in builds with DCHECKs enabled.
// Returns whether the given `feature` is enabled.
//
// If no `FeatureList` instance is registered, this returns the feature's
// default state. Registering a `FeatureList` later will fail.
//
// TODO(crbug.com/1358639): Make registering a `FeatureList` later fail on
// iOS, Android and ChromeOS. This currently only works on Windows, Mac and
// Linux.
//
// A feature with a given name must only have a single corresponding Feature
// instance, which is checked in builds with DCHECKs enabled.
static bool IsEnabled(const Feature& feature);
// Some characters are not allowed to appear in feature names or the

@ -57,8 +57,8 @@ absl::optional<int> NumberOfPhysicalProcessors() {
}
absl::optional<int> NumberOfProcessorsWhenCpuSecurityMitigationEnabled() {
if (!FeatureList::IsEnabled(kNumberOfCoresWithCpuSecurityMitigation) ||
!g_is_cpu_security_mitigation_enabled) {
if (!g_is_cpu_security_mitigation_enabled ||
!FeatureList::IsEnabled(kNumberOfCoresWithCpuSecurityMitigation)) {
return absl::nullopt;
}
return NumberOfPhysicalProcessors();

@ -27,7 +27,7 @@ namespace {
const int64_t kSmallArchiveSize = 1LL * 1024 * 1024;
const int64_t kLargeArchiveSize =
2 * PrefetchDownloaderQuota::GetMaxDailyQuotaBytes() / 3;
2 * PrefetchDownloaderQuota::kDefaultMaxDailyQuotaBytes / 3;
const PrefetchItem* FindPrefetchItemByOfflineId(
const std::set<PrefetchItem>& items,

@ -43,21 +43,20 @@ class AudioServiceTestHelper::TestingApi : public audio::mojom::TestingApi {
};
AudioServiceTestHelper::AudioServiceTestHelper()
: testing_api_(new TestingApi) {
if (base::FeatureList::IsEnabled(features::kAudioServiceOutOfProcess)) {
audio::Service::SetTestingApiBinderForTesting(
base::BindRepeating(&AudioServiceTestHelper::BindTestingApiReceiver,
base::Unretained(this)));
}
: testing_api_(std::make_unique<TestingApi>()) {
audio::Service::SetTestingApiBinderForTesting(base::BindRepeating(
&AudioServiceTestHelper::BindTestingApiReceiver, base::Unretained(this)));
}
AudioServiceTestHelper::~AudioServiceTestHelper() {
if (base::FeatureList::IsEnabled(features::kAudioServiceOutOfProcess))
audio::Service::SetTestingApiBinderForTesting(base::NullCallback());
audio::Service::SetTestingApiBinderForTesting(base::NullCallback());
}
void AudioServiceTestHelper::BindTestingApiReceiver(
mojo::PendingReceiver<audio::mojom::TestingApi> receiver) {
CHECK(base::FeatureList::IsEnabled(features::kAudioServiceOutOfProcess))
<< "Audio Service API binder shouldn't be invoked from this process when "
"the AudioServiceOutOfProcess feature is disabled.";
testing_api_->BindReceiver(std::move(receiver));
}

@ -72,12 +72,12 @@ int main(int argc, char** argv) {
gin::V8Initializer::LoadV8Snapshot();
#endif
base::SingleThreadTaskExecutor main_thread_task_executor;
base::ThreadPoolInstance::CreateAndStartWithDefaultParams("gin");
// Initialize the base::FeatureList since IsolateHolder can depend on it.
base::FeatureList::SetInstance(base::WrapUnique(new base::FeatureList));
base::SingleThreadTaskExecutor main_thread_task_executor;
base::ThreadPoolInstance::CreateAndStartWithDefaultParams("gin");
{
gin::IsolateHolder::Initialize(gin::IsolateHolder::kStrictMode,
gin::ArrayBufferAllocator::SharedInstance());

@ -32,9 +32,10 @@
namespace blink {
namespace {
const double kScrollAnimationDuration =
(::features::IsImpulseScrollAnimationEnabled() ? 1.5 : 0.5);
double ScrollAnimationDuration() {
return ::features::IsImpulseScrollAnimationEnabled() ? 1.5 : 0.5;
}
} // namespace
class FractionalScrollSimTest : public SimTest, public PaintTestConfigurations {
public:
@ -242,7 +243,7 @@ TEST_P(ScrollAnimatorSimTest, TestRootFrameLayoutViewportUserScrollCallBack) {
// The callback is executed when the animation finishes at
// ScrollAnimator::TickAnimation.
Compositor().BeginFrame();
Compositor().BeginFrame(kScrollAnimationDuration);
Compositor().BeginFrame(ScrollAnimationDuration());
ASSERT_TRUE(finished);
}
@ -286,7 +287,7 @@ TEST_P(ScrollAnimatorSimTest, TestRootFrameVisualViewporUserScrollCallBack) {
// The callback is executed when the animation finishes at
// ScrollAnimator::TickAnimation.
Compositor().BeginFrame();
Compositor().BeginFrame(kScrollAnimationDuration);
Compositor().BeginFrame(ScrollAnimationDuration());
ASSERT_TRUE(finished);
}
@ -330,7 +331,7 @@ TEST_P(ScrollAnimatorSimTest, TestRootFrameBothViewportsUserScrollCallBack) {
// The callback is executed when the animation finishes at
// ScrollAnimator::TickAnimation.
Compositor().BeginFrame();
Compositor().BeginFrame(kScrollAnimationDuration);
Compositor().BeginFrame(ScrollAnimationDuration());
ASSERT_TRUE(finished);
}
@ -380,7 +381,7 @@ TEST_P(ScrollAnimatorSimTest, TestDivUserScrollCallBack) {
// The callback is executed when the animation finishes at
// ScrollAnimator::TickAnimation.
Compositor().BeginFrame(kScrollAnimationDuration);
Compositor().BeginFrame(ScrollAnimationDuration());
ASSERT_TRUE(finished);
}