Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 /* | 1 /* |
| 2 * Copyright (C) 2011 Google Inc. All rights reserved. | 2 * Copyright (C) 2011 Google Inc. All rights reserved. |
| 3 * | 3 * |
| 4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
| 5 * modification, are permitted provided that the following conditions | 5 * modification, are permitted provided that the following conditions |
| 6 * are met: | 6 * are met: |
| 7 * | 7 * |
| 8 * 1. Redistributions of source code must retain the above copyright | 8 * 1. Redistributions of source code must retain the above copyright |
| 9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
| 10 * 2. Redistributions in binary form must reproduce the above copyright | 10 * 2. Redistributions in binary form must reproduce the above copyright |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 50 #include "modules/indexeddb/IDBValue.h" | 50 #include "modules/indexeddb/IDBValue.h" |
| 51 #include "platform/RuntimeEnabledFeatures.h" | 51 #include "platform/RuntimeEnabledFeatures.h" |
| 52 #include "platform/SharedBuffer.h" | 52 #include "platform/SharedBuffer.h" |
| 53 #include "wtf/MathExtras.h" | 53 #include "wtf/MathExtras.h" |
| 54 #include "wtf/Vector.h" | 54 #include "wtf/Vector.h" |
| 55 | 55 |
| 56 namespace blink { | 56 namespace blink { |
| 57 | 57 |
| 58 static v8::Local<v8::Value> deserializeIDBValueData(v8::Isolate*, | 58 static v8::Local<v8::Value> deserializeIDBValueData(v8::Isolate*, |
| 59 const IDBValue*); | 59 const IDBValue*); |
| 60 static v8::Local<v8::Value> deserializeIDBValue( | |
| 61 v8::Isolate*, | |
| 62 v8::Local<v8::Object> creationContext, | |
| 63 const IDBValue*); | |
| 64 static v8::Local<v8::Value> deserializeIDBValueArray( | 60 static v8::Local<v8::Value> deserializeIDBValueArray( |
| 65 v8::Isolate*, | 61 v8::Isolate*, |
| 66 v8::Local<v8::Object> creationContext, | 62 v8::Local<v8::Object> creationContext, |
| 67 const Vector<RefPtr<IDBValue>>*); | 63 const Vector<RefPtr<IDBValue>>*); |
| 68 | 64 |
| 69 v8::Local<v8::Value> ToV8(const IDBKeyPath& value, | 65 v8::Local<v8::Value> ToV8(const IDBKeyPath& value, |
| 70 v8::Local<v8::Object> creationContext, | 66 v8::Local<v8::Object> creationContext, |
| 71 v8::Isolate* isolate) { | 67 v8::Isolate* isolate) { |
| 72 switch (value.getType()) { | 68 switch (value.getType()) { |
| 73 case IDBKeyPath::NullType: | 69 case IDBKeyPath::NullType: |
| (...skipping 297 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 371 } | 367 } |
| 372 return IDBKey::createArray(result); | 368 return IDBKey::createArray(result); |
| 373 } | 369 } |
| 374 | 370 |
| 375 ASSERT(keyPath.getType() == IDBKeyPath::StringType); | 371 ASSERT(keyPath.getType() == IDBKeyPath::StringType); |
| 376 return createIDBKeyFromValueAndKeyPath(isolate, value, keyPath.string(), | 372 return createIDBKeyFromValueAndKeyPath(isolate, value, keyPath.string(), |
| 377 exceptionState); | 373 exceptionState); |
| 378 } | 374 } |
| 379 | 375 |
| 380 // Deserialize just the value data & blobInfo from the given IDBValue. | 376 // Deserialize just the value data & blobInfo from the given IDBValue. |
| 381 // Does not deserialize the key & keypath. | 377 // |
| 378 // Primary key injection is performed in deserializeIDBValue() below. | |
| 382 static v8::Local<v8::Value> deserializeIDBValueData(v8::Isolate* isolate, | 379 static v8::Local<v8::Value> deserializeIDBValueData(v8::Isolate* isolate, |
| 383 const IDBValue* value) { | 380 const IDBValue* value) { |
| 384 ASSERT(isolate->InContext()); | 381 ASSERT(isolate->InContext()); |
| 385 if (!value || value->isNull()) | 382 if (!value || value->isNull()) |
| 386 return v8::Null(isolate); | 383 return v8::Null(isolate); |
| 387 | 384 |
| 388 RefPtr<SerializedScriptValue> serializedValue = | 385 RefPtr<SerializedScriptValue> serializedValue = |
| 389 value->createSerializedValue(); | 386 value->createSerializedValue(); |
| 390 SerializedScriptValue::DeserializeOptions options; | 387 SerializedScriptValue::DeserializeOptions options; |
| 391 options.blobInfo = value->blobInfo(); | 388 options.blobInfo = value->blobInfo(); |
| 392 options.readWasmFromStream = true; | 389 options.readWasmFromStream = true; |
| 390 | |
| 391 // deserialize() returns null when serialization fails. This is sub-optimal | |
| 392 // because IndexedDB values can be null, so an application cannot distinguish | |
| 393 // between a de-serialization failure and a legitimately stored null value. | |
| 394 // | |
| 395 // TODO(crbug.com/703704): Ideally, SerializedScriptValue should return an | |
| 396 // empty handle on serialization errors, which should be handled by higher | |
| 397 // layers. For example, IndexedDB could throw an exception, abort the | |
| 398 // transaction, or close the database connection. | |
| 393 return serializedValue->deserialize(isolate, options); | 399 return serializedValue->deserialize(isolate, options); |
| 394 } | 400 } |
| 395 | 401 |
| 396 // Deserialize the entire IDBValue (injecting key & keypath if present). | 402 // Deserialize the entire IDBValue. |
| 397 static v8::Local<v8::Value> deserializeIDBValue( | 403 // |
| 398 v8::Isolate* isolate, | 404 // On top of deserializeIDBValueData(), this handles the special case of having |
| 399 v8::Local<v8::Object> creationContext, | 405 // to inject a key into the de-serialized value. See injectV8KeyIntoV8Value() |
| 400 const IDBValue* value) { | 406 // for details. |
| 407 v8::Local<v8::Value> deserializeIDBValue(v8::Isolate* isolate, | |
| 408 v8::Local<v8::Object> creationContext, | |
| 409 const IDBValue* value) { | |
| 401 ASSERT(isolate->InContext()); | 410 ASSERT(isolate->InContext()); |
| 402 if (!value || value->isNull()) | 411 if (!value || value->isNull()) |
| 403 return v8::Null(isolate); | 412 return v8::Null(isolate); |
| 404 | 413 |
| 405 v8::Local<v8::Value> v8Value = deserializeIDBValueData(isolate, value); | 414 v8::Local<v8::Value> v8Value = deserializeIDBValueData(isolate, value); |
| 406 if (value->primaryKey()) { | 415 if (value->primaryKey()) { |
| 407 v8::Local<v8::Value> key = | 416 v8::Local<v8::Value> key = |
| 408 ToV8(value->primaryKey(), creationContext, isolate); | 417 ToV8(value->primaryKey(), creationContext, isolate); |
| 409 if (key.IsEmpty()) | 418 if (key.IsEmpty()) |
| 410 return v8::Local<v8::Value>(); | 419 return v8::Local<v8::Value>(); |
|
jsbell
2017/04/04 16:42:33
Aside: I wonder if we test this code path?
pwnall
2017/04/04 22:41:42
Nope.
I looked at it... then I thought of what I'
jsbell
2017/04/05 17:56:07
Tracking bug is fine.
(Without exploring all of
| |
| 411 bool injected = | 420 |
| 412 injectV8KeyIntoV8Value(isolate, key, v8Value, value->keyPath()); | 421 injectV8KeyIntoV8Value(isolate, key, v8Value, value->keyPath()); |
| 413 DCHECK(injected); | 422 |
| 423 // TODO(crbug.com/703704): Throw an error here or at a higher layer if | |
| 424 // injectV8KeyIntoV8Value() returns false, which means that the serialized | |
| 425 // value got corrupted while on disk. | |
| 414 } | 426 } |
| 415 | 427 |
| 416 return v8Value; | 428 return v8Value; |
| 417 } | 429 } |
| 418 | 430 |
| 419 static v8::Local<v8::Value> deserializeIDBValueArray( | 431 static v8::Local<v8::Value> deserializeIDBValueArray( |
| 420 v8::Isolate* isolate, | 432 v8::Isolate* isolate, |
| 421 v8::Local<v8::Object> creationContext, | 433 v8::Local<v8::Object> creationContext, |
| 422 const Vector<RefPtr<IDBValue>>* values) { | 434 const Vector<RefPtr<IDBValue>>* values) { |
| 423 ASSERT(isolate->InContext()); | 435 ASSERT(isolate->InContext()); |
| 424 | 436 |
| 425 v8::Local<v8::Context> context = isolate->GetCurrentContext(); | 437 v8::Local<v8::Context> context = isolate->GetCurrentContext(); |
| 426 v8::Local<v8::Array> array = v8::Array::New(isolate, values->size()); | 438 v8::Local<v8::Array> array = v8::Array::New(isolate, values->size()); |
| 427 for (size_t i = 0; i < values->size(); ++i) { | 439 for (size_t i = 0; i < values->size(); ++i) { |
| 428 v8::Local<v8::Value> v8Value = | 440 v8::Local<v8::Value> v8Value = |
| 429 deserializeIDBValue(isolate, creationContext, values->at(i).get()); | 441 deserializeIDBValue(isolate, creationContext, values->at(i).get()); |
| 430 if (v8Value.IsEmpty()) | 442 if (v8Value.IsEmpty()) |
| 431 v8Value = v8::Undefined(isolate); | 443 v8Value = v8::Undefined(isolate); |
| 432 if (!v8CallBoolean(array->CreateDataProperty(context, i, v8Value))) | 444 if (!v8CallBoolean(array->CreateDataProperty(context, i, v8Value))) |
| 433 return v8Undefined(); | 445 return v8Undefined(); |
| 434 } | 446 } |
| 435 | 447 |
| 436 return array; | 448 return array; |
| 437 } | 449 } |
| 438 | 450 |
| 439 // This is only applied to deserialized values which were validated before | 451 // Injects a primary key into a deserialized V8 value. |
| 440 // serialization, so various assumptions/assertions can be made. | 452 // |
| 453 // In general, the value stored in IndexedDB is the serialized version of a | |
| 454 // value passed to the API. However, the specification has a special case of | |
| 455 // object stores that specify a key path and have a key generator. In this case, | |
| 456 // the conceptual description in the spec states that the key produced by the | |
| 457 // key generator is injected into the value before it is written to IndexedDB. | |
| 458 // | |
| 459 // We cannot implementing the spec's conceptual description. We need to assign | |
| 460 // primary keys in the browser process, to ensure that multiple renderer | |
| 461 // processes talking to the same database receive sequential keys. At the same | |
| 462 // time, we want the value serialization code to live in the renderer process, | |
| 463 // because this type of code is a likely victim to security exploits. | |
| 464 // | |
| 465 // We handle this special case by serializing and writing values without the | |
| 466 // corresponding keys. At read time, we obtain the keys and the values | |
| 467 // separately, and we inject the keys into values. | |
| 441 bool injectV8KeyIntoV8Value(v8::Isolate* isolate, | 468 bool injectV8KeyIntoV8Value(v8::Isolate* isolate, |
| 442 v8::Local<v8::Value> key, | 469 v8::Local<v8::Value> key, |
| 443 v8::Local<v8::Value> value, | 470 v8::Local<v8::Value> value, |
| 444 const IDBKeyPath& keyPath) { | 471 const IDBKeyPath& keyPath) { |
| 445 IDB_TRACE("injectIDBV8KeyIntoV8Value"); | 472 IDB_TRACE("injectIDBV8KeyIntoV8Value"); |
| 446 ASSERT(isolate->InContext()); | 473 ASSERT(isolate->InContext()); |
| 447 | 474 |
| 448 ASSERT(keyPath.getType() == IDBKeyPath::StringType); | 475 ASSERT(keyPath.getType() == IDBKeyPath::StringType); |
| 449 Vector<String> keyPathElements = parseKeyPath(keyPath.string()); | 476 Vector<String> keyPathElements = parseKeyPath(keyPath.string()); |
| 450 | 477 |
| 451 // The conbination of a key generator and an empty key path is forbidden by | 478 // The conbination of a key generator and an empty key path is forbidden by |
| 452 // spec. | 479 // spec. |
| 453 if (!keyPathElements.size()) { | 480 if (!keyPathElements.size()) { |
| 454 ASSERT_NOT_REACHED(); | 481 ASSERT_NOT_REACHED(); |
| 455 return false; | 482 return false; |
| 456 } | 483 } |
| 457 | 484 |
| 458 v8::HandleScope handleScope(isolate); | 485 v8::HandleScope handleScope(isolate); |
| 459 v8::Local<v8::Context> context = isolate->GetCurrentContext(); | 486 v8::Local<v8::Context> context = isolate->GetCurrentContext(); |
| 460 | 487 |
| 461 // For an object o = {} which should have keypath 'a.b.c' and key k, this | 488 // For an object o = {} which should have keypath 'a.b.c' and key k, this |
| 462 // populates o to be {a:{b:{}}}. This is only applied to deserialized | 489 // populates o to be {a:{b:{}}}. This is only applied to deserialized |
| 463 // values which were validated before serialization, so various | 490 // values, so we can assume that there are no getters/setters on the |
| 464 // assumptions can be made, e.g. there are no getters/setters on the | |
| 465 // object itself (though there might be on the prototype chain). | 491 // object itself (though there might be on the prototype chain). |
| 492 // | |
| 493 // Previous versions of this code assumed that the deserialized value meets | |
| 494 // the constraints checked by the serialization validation code. For example, | |
| 495 // given a keypath of a.b.c, the code assumed that the de-serialized value | |
| 496 // cannot possibly be {a:{b:42}}. This is not a safe assumption. | |
| 497 // | |
| 498 // IndexedDB's backing store (LevelDB) does use CRC32C to protect against disk | |
| 499 // errors. However, this does not prevent corruption caused by bugs in the | |
| 500 // higher level code writing invalid values. The following cases are | |
| 501 // interesting here. | |
| 502 // | |
| 503 // (1) Deserialization failures, which are currently handled by returning | |
| 504 // null. Disk errors aside, deserialization errors can also occur when a user | |
| 505 // switches channels and receives an older build which does not support the | |
| 506 // serialization format used by the previous (more recent) build that the user | |
| 507 // had. | |
| 508 // | |
| 509 // (2) Bugs that write a value which is incompatible with the primary key | |
| 510 // injection required by the object store. The simplest example is writing | |
| 511 // numbers or booleans to an object store with an auto-incrementing primary | |
| 512 // keys. | |
| 466 for (size_t i = 0; i < keyPathElements.size() - 1; ++i) { | 513 for (size_t i = 0; i < keyPathElements.size() - 1; ++i) { |
| 514 if (!value->IsObject()) | |
| 515 return false; | |
| 516 | |
| 467 const String& keyPathElement = keyPathElements[i]; | 517 const String& keyPathElement = keyPathElements[i]; |
| 468 ASSERT(value->IsObject()); | |
| 469 ASSERT(!isImplicitProperty(isolate, value, keyPathElement)); | 518 ASSERT(!isImplicitProperty(isolate, value, keyPathElement)); |
| 470 v8::Local<v8::Object> object = value.As<v8::Object>(); | 519 v8::Local<v8::Object> object = value.As<v8::Object>(); |
| 471 v8::Local<v8::String> property = v8String(isolate, keyPathElement); | 520 v8::Local<v8::String> property = v8String(isolate, keyPathElement); |
| 472 bool hasOwnProperty; | 521 bool hasOwnProperty; |
| 473 if (!v8Call(object->HasOwnProperty(context, property), hasOwnProperty)) | 522 if (!v8Call(object->HasOwnProperty(context, property), hasOwnProperty)) |
| 474 return false; | 523 return false; |
| 475 if (hasOwnProperty) { | 524 if (hasOwnProperty) { |
| 476 if (!object->Get(context, property).ToLocal(&value)) | 525 if (!object->Get(context, property).ToLocal(&value)) |
| 477 return false; | 526 return false; |
| 478 } else { | 527 } else { |
| 479 value = v8::Object::New(isolate); | 528 value = v8::Object::New(isolate); |
| 480 if (!v8CallBoolean(object->CreateDataProperty(context, property, value))) | 529 if (!v8CallBoolean(object->CreateDataProperty(context, property, value))) |
| 481 return false; | 530 return false; |
| 482 } | 531 } |
| 483 } | 532 } |
| 484 | 533 |
| 485 // Implicit properties don't need to be set. The caller is not required to | 534 // Implicit properties don't need to be set. The caller is not required to |
| 486 // be aware of this, so this is an expected no-op. The caller can verify | 535 // be aware of this, so this is an expected no-op. The caller can verify |
| 487 // that the value is correct via assertPrimaryKeyValidOrInjectable. | 536 // that the value is correct via assertPrimaryKeyValidOrInjectable. |
| 488 if (isImplicitProperty(isolate, value, keyPathElements.back())) | 537 if (isImplicitProperty(isolate, value, keyPathElements.back())) |
| 489 return true; | 538 return true; |
| 490 | 539 |
| 491 // If it's not an implicit property of value, value must be an object. | 540 // If the key path does not point to an implicit property, the value must be |
| 541 // an object. Previous code versions DCHECKed this, which is unsafe in the | |
| 542 // event of database corruption. | |
|
jsbell
2017/04/04 16:42:33
maybe append "or undetected version skew."
pwnall
2017/04/04 22:41:42
Done.
| |
| 543 if (!value->IsObject()) | |
| 544 return false; | |
| 545 | |
| 492 v8::Local<v8::Object> object = value.As<v8::Object>(); | 546 v8::Local<v8::Object> object = value.As<v8::Object>(); |
| 493 v8::Local<v8::String> property = v8String(isolate, keyPathElements.back()); | 547 v8::Local<v8::String> property = v8String(isolate, keyPathElements.back()); |
| 494 if (!v8CallBoolean(object->CreateDataProperty(context, property, key))) | 548 if (!v8CallBoolean(object->CreateDataProperty(context, property, key))) |
| 495 return false; | 549 return false; |
| 496 | 550 |
| 497 return true; | 551 return true; |
| 498 } | 552 } |
| 499 | 553 |
| 500 // Verify that an value can have an generated key inserted at the location | 554 // Verify that an value can have an generated key inserted at the location |
| 501 // specified by the key path (by injectV8KeyIntoV8Value) when the object is | 555 // specified by the key path (by injectV8KeyIntoV8Value) when the object is |
| (...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 609 if (expectedKey && expectedKey->isEqual(value->primaryKey())) | 663 if (expectedKey && expectedKey->isEqual(value->primaryKey())) |
| 610 return; | 664 return; |
| 611 | 665 |
| 612 bool injected = injectV8KeyIntoV8Value( | 666 bool injected = injectV8KeyIntoV8Value( |
| 613 isolate, keyValue.v8Value(), scriptValue.v8Value(), value->keyPath()); | 667 isolate, keyValue.v8Value(), scriptValue.v8Value(), value->keyPath()); |
| 614 DCHECK(injected); | 668 DCHECK(injected); |
| 615 } | 669 } |
| 616 #endif | 670 #endif |
| 617 | 671 |
| 618 } // namespace blink | 672 } // namespace blink |
| OLD | NEW |