Skip to content

Commit eaffb82

Browse files
isheludkomibrunin
authored andcommitted
[Backport] Security bug 1201938
Manual cherry-pick of patch originally reviewed on https://chromium-review.googlesource.com/c/v8/v8/+/2880214: Merged: [const-tracking] Generalize constness when delete properties Revision: d570bbe0c74ec4ae40d1abc34bea617ff2d63f26 BUG=chromium:1201938 NOTRY=true NOPRESUBMIT=true NOTREECHECKS=true [email protected] Change-Id: I2745bd574d9f971b3f1e41d5084ec9e9fbbeef07 Reviewed-by: Leszek Swirski <[email protected]> Cr-Commit-Position: refs/branch-heads/9.0@{#55} Cr-Branched-From: bd0108b4c88e0d6f2350cb79b5f363fbd02f3eb7-refs/heads/9.0.257@{#1} Cr-Branched-From: 349bcc6a075411f1a7ce2d866c3dfeefc2efa39d-refs/heads/master@{#73001} Reviewed-by: Allan Sandfeld Jensen <[email protected]>
1 parent 951cdb3 commit eaffb82

File tree

11 files changed

+171
-71
lines changed

11 files changed

+171
-71
lines changed

chromium/v8/src/base/platform/mutex.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "src/base/win32-headers.h"
1212
#endif
1313
#include "src/base/logging.h"
14+
#include "src/base/optional.h"
1415

1516
#if V8_OS_POSIX
1617
#include <pthread.h> // NOLINT
@@ -340,6 +341,20 @@ class SharedMutexGuard final {
340341
DISALLOW_COPY_AND_ASSIGN(SharedMutexGuard);
341342
};
342343

344+
template <MutexSharedType kIsShared,
345+
NullBehavior Behavior = NullBehavior::kRequireNotNull>
346+
class SharedMutexGuardIf final {
347+
public:
348+
SharedMutexGuardIf(SharedMutex* mutex, bool enable_mutex) {
349+
if (enable_mutex) mutex_.emplace(mutex);
350+
}
351+
352+
private:
353+
base::Optional<SharedMutexGuard<kIsShared, Behavior>> mutex_;
354+
355+
DISALLOW_COPY_AND_ASSIGN(SharedMutexGuardIf);
356+
};
357+
343358
} // namespace base
344359
} // namespace v8
345360

chromium/v8/src/builtins/builtins-internal-gen.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -508,8 +508,8 @@ TF_BUILTIN(DeleteProperty, DeletePropertyBaseAssembler) {
508508
Label dictionary(this), dont_delete(this);
509509
GotoIf(IsDictionaryMap(receiver_map), &dictionary);
510510

511-
// Fast properties need to clear recorded slots, which can only be done
512-
// in C++.
511+
// Fast properties need to clear recorded slots and mark the deleted
512+
// property as mutable, which can only be done in C++.
513513
Goto(&slow);
514514

515515
BIND(&dictionary);

chromium/v8/src/objects/internal-index.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ class InternalIndex {
4545
return static_cast<int>(entry_);
4646
}
4747

48-
bool operator==(const InternalIndex& other) { return entry_ == other.entry_; }
48+
bool operator==(const InternalIndex& other) const { return entry_ == other.entry_; }
4949

5050
// Iteration support.
5151
InternalIndex operator*() { return *this; }
52-
bool operator!=(const InternalIndex& other) { return entry_ != other.entry_; }
52+
bool operator!=(const InternalIndex& other) const { return entry_ != other.entry_; }
5353
InternalIndex& operator++() {
5454
entry_++;
5555
return *this;

chromium/v8/src/objects/map-inl.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,10 @@ bool Map::TooManyFastProperties(StoreOrigin store_origin) const {
185185
}
186186
}
187187

188+
Name Map::GetLastDescriptorName(Isolate* isolate) const {
189+
return instance_descriptors(isolate).GetKey(LastAdded());
190+
}
191+
188192
PropertyDetails Map::GetLastDescriptorDetails(Isolate* isolate) const {
189193
return instance_descriptors(isolate).GetDetails(LastAdded());
190194
}

chromium/v8/src/objects/map-updater.cc

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -121,50 +121,6 @@ Handle<Map> MapUpdater::ReconfigureToDataField(InternalIndex descriptor,
121121
PropertyDetails old_details =
122122
old_descriptors_->GetDetails(modified_descriptor_);
123123

124-
// If the {descriptor} was "const" data field so far, we need to update the
125-
// {old_map_} here, otherwise we could get the constants wrong, i.e.
126-
//
127-
// o.x = 1;
128-
// change o.x's attributes to something else
129-
// delete o.x;
130-
// o.x = 2;
131-
//
132-
// could trick V8 into thinking that `o.x` is still 1 even after the second
133-
// assignment.
134-
// This situation is similar to what might happen with property deletion.
135-
if (old_details.constness() == PropertyConstness::kConst &&
136-
old_details.location() == kField &&
137-
old_details.attributes() != new_attributes_) {
138-
// Ensure we'll be updating constness of the up-to-date version of old_map_.
139-
Handle<Map> old_map = Map::Update(isolate_, old_map_);
140-
PropertyDetails details =
141-
old_map->instance_descriptors().GetDetails(descriptor);
142-
Handle<FieldType> field_type(
143-
old_map->instance_descriptors().GetFieldType(descriptor),
144-
isolate_);
145-
Map::GeneralizeField(isolate_, old_map, descriptor,
146-
PropertyConstness::kMutable, details.representation(),
147-
field_type);
148-
DCHECK_EQ(PropertyConstness::kMutable,
149-
old_map->instance_descriptors()
150-
.GetDetails(descriptor)
151-
.constness());
152-
// The old_map_'s property must become mutable.
153-
// Note, that the {old_map_} and {old_descriptors_} are not expected to be
154-
// updated by the generalization if the map is already deprecated.
155-
DCHECK_IMPLIES(
156-
!old_map_->is_deprecated(),
157-
PropertyConstness::kMutable ==
158-
old_descriptors_->GetDetails(modified_descriptor_).constness());
159-
// Although the property in the old map is marked as mutable we still
160-
// treat it as constant when merging with the new path in transition tree.
161-
// This is fine because up until this reconfiguration the field was
162-
// known to be constant, so it's fair to proceed treating it as such
163-
// during this reconfiguration session. The issue is that after the
164-
// reconfiguration the original field might become mutable (see the delete
165-
// example above).
166-
}
167-
168124
// If property kind is not reconfigured merge the result with
169125
// representation/field type from the old descriptor.
170126
if (old_details.kind() == new_kind_) {

chromium/v8/src/objects/map.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,7 @@ class Map : public HeapObject {
639639
// chain state.
640640
inline bool IsPrototypeValidityCellValid() const;
641641

642+
inline Name GetLastDescriptorName(Isolate* isolate) const;
642643
inline PropertyDetails GetLastDescriptorDetails(Isolate* isolate) const;
643644

644645
inline InternalIndex LastAdded() const;

chromium/v8/src/objects/property-details.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ enum PropertyAttributes {
3232
// a non-existent property.
3333
};
3434

35+
// Number of distinct bits in PropertyAttributes.
36+
static const int kPropertyAttributesBitsCount = 3;
37+
38+
static const int kPropertyAttributesCombinationsCount =
39+
1 << kPropertyAttributesBitsCount;
40+
3541
enum PropertyFilter {
3642
ALL_PROPERTIES = 0,
3743
ONLY_WRITABLE = 1,
@@ -63,6 +69,11 @@ STATIC_ASSERT(SKIP_STRINGS ==
6369
STATIC_ASSERT(SKIP_SYMBOLS ==
6470
static_cast<PropertyFilter>(v8::PropertyFilter::SKIP_SYMBOLS));
6571

72+
// Assert that kPropertyAttributesBitsCount value matches the definition of
73+
// ALL_ATTRIBUTES_MASK.
74+
STATIC_ASSERT((ALL_ATTRIBUTES_MASK == (READ_ONLY | DONT_ENUM | DONT_DELETE)) ==
75+
(kPropertyAttributesBitsCount == 3));
76+
6677
class Smi;
6778
class TypeInfo;
6879

chromium/v8/src/objects/transitions.cc

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,34 @@ MaybeHandle<Map> TransitionsAccessor::FindTransitionToDataProperty(
272272
return Handle<Map>(target, isolate_);
273273
}
274274

275+
void TransitionsAccessor::ForEachTransitionTo(
276+
Name name, const ForEachTransitionCallback& callback,
277+
DisallowHeapAllocation* no_gc) {
278+
DCHECK(name.IsUniqueName());
279+
switch (encoding()) {
280+
case kPrototypeInfo:
281+
case kUninitialized:
282+
case kMigrationTarget:
283+
return;
284+
case kWeakRef: {
285+
Map target = Map::cast(raw_transitions_->GetHeapObjectAssumeWeak());
286+
InternalIndex descriptor = target.LastAdded();
287+
DescriptorArray descriptors = target.instance_descriptors();
288+
Name key = descriptors.GetKey(descriptor);
289+
if (key == name) {
290+
callback(target);
291+
}
292+
return;
293+
}
294+
case kFullTransitionArray: {
295+
v8::base::SharedMutexGuardIf<base::kShared> scope(
296+
isolate_->transition_array_access(), concurrent_access_);
297+
return transitions().ForEachTransitionTo(name, callback);
298+
}
299+
}
300+
UNREACHABLE();
301+
}
302+
275303
bool TransitionsAccessor::CanHaveMoreTransitions() {
276304
if (map_.is_dictionary_map()) return false;
277305
if (encoding() == kFullTransitionArray) {
@@ -613,6 +641,21 @@ Map TransitionArray::SearchAndGetTarget(PropertyKind kind, Name name,
613641
return SearchDetailsAndGetTarget(transition, kind, attributes);
614642
}
615643

644+
void TransitionArray::ForEachTransitionTo(
645+
Name name, const ForEachTransitionCallback& callback) {
646+
int transition = SearchName(name, nullptr);
647+
if (transition == kNotFound) return;
648+
649+
int nof_transitions = number_of_transitions();
650+
DCHECK(transition < nof_transitions);
651+
Name key = GetKey(transition);
652+
for (; transition < nof_transitions && GetKey(transition) == key;
653+
transition++) {
654+
Map target = GetTarget(transition);
655+
callback(target);
656+
}
657+
}
658+
616659
void TransitionArray::Sort() {
617660
DisallowHeapAllocation no_gc;
618661
// In-place insertion sort.

chromium/v8/src/objects/transitions.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
namespace v8 {
2020
namespace internal {
2121

22+
// Find all transitions with given name and calls the callback.
23+
using ForEachTransitionCallback = std::function<void(Map)>;
24+
2225
// TransitionsAccessor is a helper class to encapsulate access to the various
2326
// ways a Map can store transitions to other maps in its respective field at
2427
// Map::kTransitionsOrPrototypeInfo.
@@ -68,6 +71,14 @@ class V8_EXPORT_PRIVATE TransitionsAccessor {
6871
return FindTransitionToDataProperty(name, kFieldOnly);
6972
}
7073

74+
// Find all transitions with given name and calls the callback.
75+
// Neither GCs nor operations requiring Isolate::full_transition_array_access
76+
// lock are allowed inside the callback.
77+
// If any of the GC- or lock-requiring processing is necessary, it has to be
78+
// done outside of the callback.
79+
void ForEachTransitionTo(Name name, const ForEachTransitionCallback& callback,
80+
DisallowHeapAllocation* no_gc);
81+
7182
inline Handle<String> ExpectedTransitionKey();
7283
inline Handle<Map> ExpectedTransitionTarget();
7384

@@ -320,6 +331,10 @@ class TransitionArray : public WeakFixedArray {
320331
Map SearchDetailsAndGetTarget(int transition, PropertyKind kind,
321332
PropertyAttributes attributes);
322333

334+
// Find all transitions with given name and calls the callback.
335+
void ForEachTransitionTo(Name name,
336+
const ForEachTransitionCallback& callback);
337+
323338
inline int number_of_transitions() const;
324339

325340
static bool CompactPrototypeTransitionArray(Isolate* isolate,

chromium/v8/src/runtime/runtime-object.cc

Lines changed: 75 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "src/objects/js-array-inl.h"
1818
#include "src/objects/property-descriptor-object.h"
1919
#include "src/objects/property-descriptor.h"
20+
#include "src/objects/property-details.h"
2021
#include "src/runtime/runtime-utils.h"
2122
#include "src/runtime/runtime.h"
2223

@@ -93,6 +94,54 @@ MaybeHandle<Object> Runtime::HasProperty(Isolate* isolate,
9394

9495
namespace {
9596

97+
void GeneralizeAllTransitionsToFieldAsMutable(Isolate* isolate, Handle<Map> map,
98+
Handle<Name> name) {
99+
InternalIndex descriptor(map->NumberOfOwnDescriptors());
100+
101+
Handle<Map> target_maps[kPropertyAttributesCombinationsCount];
102+
int target_maps_count = 0;
103+
104+
// Collect all outgoing field transitions.
105+
{
106+
DisallowHeapAllocation no_gc;
107+
TransitionsAccessor transitions(isolate, *map, &no_gc);
108+
transitions.ForEachTransitionTo(
109+
*name,
110+
[&](Map target) {
111+
DCHECK_EQ(descriptor, target.LastAdded());
112+
DCHECK_EQ(*name, target.GetLastDescriptorName(isolate));
113+
PropertyDetails details = target.GetLastDescriptorDetails(isolate);
114+
// Currently, we track constness only for fields.
115+
if (details.kind() == kData &&
116+
details.constness() == PropertyConstness::kConst) {
117+
target_maps[target_maps_count++] = handle(target, isolate);
118+
}
119+
DCHECK_IMPLIES(details.kind() == kAccessor,
120+
details.constness() == PropertyConstness::kConst);
121+
},
122+
&no_gc);
123+
CHECK_LE(target_maps_count, kPropertyAttributesCombinationsCount);
124+
}
125+
126+
for (int i = 0; i < target_maps_count; i++) {
127+
Handle<Map> target = target_maps[i];
128+
PropertyDetails details =
129+
target->instance_descriptors(isolate)
130+
.GetDetails(descriptor);
131+
Handle<FieldType> field_type(
132+
target->instance_descriptors(isolate)
133+
.GetFieldType(descriptor),
134+
isolate);
135+
Map::GeneralizeField(isolate, target, descriptor,
136+
PropertyConstness::kMutable, details.representation(),
137+
field_type);
138+
DCHECK_EQ(PropertyConstness::kMutable,
139+
target->instance_descriptors(isolate)
140+
.GetDetails(descriptor)
141+
.constness());
142+
}
143+
}
144+
96145
bool DeleteObjectPropertyFast(Isolate* isolate, Handle<JSReceiver> receiver,
97146
Handle<Object> raw_key) {
98147
// This implements a special case for fast property deletion: when the
@@ -102,6 +151,8 @@ bool DeleteObjectPropertyFast(Isolate* isolate, Handle<JSReceiver> receiver,
102151
// (1) The receiver must be a regular object and the key a unique name.
103152
Handle<Map> receiver_map(receiver->map(), isolate);
104153
if (receiver_map->IsSpecialReceiverMap()) return false;
154+
DCHECK(receiver_map->IsJSObjectMap());
155+
105156
if (!raw_key->IsUniqueName()) return false;
106157
Handle<Name> key = Handle<Name>::cast(raw_key);
107158
// (2) The property to be deleted must be the last property.
@@ -124,26 +175,6 @@ bool DeleteObjectPropertyFast(Isolate* isolate, Handle<JSReceiver> receiver,
124175

125176
// Preconditions successful. No more bailouts after this point.
126177

127-
// If the {descriptor} was "const" so far, we need to update the
128-
// {receiver_map} here, otherwise we could get the constants wrong, i.e.
129-
//
130-
// o.x = 1;
131-
// delete o.x;
132-
// o.x = 2;
133-
//
134-
// could trick V8 into thinking that `o.x` is still 1 even after the second
135-
// assignment.
136-
if (details.constness() == PropertyConstness::kConst &&
137-
details.location() == kField) {
138-
Handle<FieldType> field_type(descriptors->GetFieldType(descriptor),
139-
isolate);
140-
Map::GeneralizeField(isolate, receiver_map, descriptor,
141-
PropertyConstness::kMutable, details.representation(),
142-
field_type);
143-
DCHECK_EQ(PropertyConstness::kMutable,
144-
descriptors->GetDetails(descriptor).constness());
145-
}
146-
147178
// Zap the property to avoid keeping objects alive. Zapping is not necessary
148179
// for properties stored in the descriptor array.
149180
if (details.location() == kField) {
@@ -190,6 +221,30 @@ bool DeleteObjectPropertyFast(Isolate* isolate, Handle<JSReceiver> receiver,
190221
receiver->HeapObjectVerify(isolate);
191222
receiver->property_array().PropertyArrayVerify(isolate);
192223
#endif
224+
225+
// If the {descriptor} was "const" so far, we need to update the
226+
// {receiver_map} here, otherwise we could get the constants wrong, i.e.
227+
//
228+
// o.x = 1;
229+
// [change o.x's attributes or reconfigure property kind]
230+
// delete o.x;
231+
// o.x = 2;
232+
//
233+
// could trick V8 into thinking that `o.x` is still 1 even after the second
234+
// assignment.
235+
236+
// Step 1: Migrate object to an up-to-date shape.
237+
if (parent_map->is_deprecated()) {
238+
JSObject::MigrateInstance(isolate, Handle<JSObject>::cast(receiver));
239+
parent_map = handle(receiver->map(), isolate);
240+
}
241+
242+
// Step 2: Mark outgoing transitions from the up-to-date version of the
243+
// parent_map to same property name of any kind or attributes as mutable.
244+
// Also migrate object to the up-to-date map to make the object shapes
245+
// converge sooner.
246+
GeneralizeAllTransitionsToFieldAsMutable(isolate, parent_map, key);
247+
193248
return true;
194249
}
195250

chromium/v8/src/runtime/runtime.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -749,9 +749,9 @@ class Runtime : public AllStatic {
749749
// Get the runtime intrinsic function table.
750750
static const Function* RuntimeFunctionTable(Isolate* isolate);
751751

752-
V8_WARN_UNUSED_RESULT static Maybe<bool> DeleteObjectProperty(
753-
Isolate* isolate, Handle<JSReceiver> receiver, Handle<Object> key,
754-
LanguageMode language_mode);
752+
V8_EXPORT_PRIVATE V8_WARN_UNUSED_RESULT static Maybe<bool>
753+
DeleteObjectProperty(Isolate* isolate, Handle<JSReceiver> receiver,
754+
Handle<Object> key, LanguageMode language_mode);
755755

756756
V8_EXPORT_PRIVATE V8_WARN_UNUSED_RESULT static MaybeHandle<Object>
757757
SetObjectProperty(Isolate* isolate, Handle<Object> object, Handle<Object> key,

0 commit comments

Comments
 (0)