Changeset 145435 in webkit


Ignore:
Timestamp:
Mar 11, 2013 4:57:21 PM (11 years ago)
Author:
alecflett@chromium.org
Message:

IndexedDB: Protect against key prefix overflows
https://bugs.webkit.org/show_bug.cgi?id=111138

Reviewed by Tony Chang.

Source/WebCore:

This reworks the boundary checking for all databaseId,
objectStoreId, and indexId, including negative and
zero-based ids. All entrypoints into IDBLevelDBCoding
are protected with explicit checks and all internal
uses of KeyPrefix are protected with ASSERTs in the
various constructors.

Tests: WebKit unit tests IDBBackingStoreTest.cpp in WebKit/chromium

  • Modules/indexeddb/IDBBackingStore.h: Make all public methods boolean-based for errors.
  • Modules/indexeddb/IDBLevelDBCoding.h: Add methods for checking databaseId, objectStoreId, and indexId.

Source/WebKit/chromium:

Add tests for invalid indexIds in basic get/put operations.

Location:
trunk/Source
Files:
11 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r145433 r145435  
     12013-03-11  Alec Flett  <alecflett@chromium.org>
     2
     3        IndexedDB: Protect against key prefix overflows
     4        https://bugs.webkit.org/show_bug.cgi?id=111138
     5
     6        Reviewed by Tony Chang.
     7
     8        This reworks the boundary checking for all databaseId,
     9        objectStoreId, and indexId, including negative and
     10        zero-based ids. All entrypoints into IDBLevelDBCoding
     11        are protected with explicit checks and all internal
     12        uses of KeyPrefix are protected with ASSERTs in the
     13        various constructors.
     14
     15        Tests: WebKit unit tests IDBBackingStoreTest.cpp in WebKit/chromium
     16
     17        * Modules/indexeddb/IDBBackingStore.h: Make all public methods boolean-based for errors.
     18        * Modules/indexeddb/IDBLevelDBCoding.h: Add methods for checking databaseId, objectStoreId, and indexId.
     19
    1202013-03-11  Philip Rogers  <pdr@google.com>
    221
  • trunk/Source/WebCore/Modules/indexeddb/IDBBackingStore.cpp

    r145385 r145435  
    637637
    638638// FIXME: This should do some error handling rather than plowing ahead when bad data is encountered.
    639 void IDBBackingStore::getObjectStores(int64_t databaseId, IDBDatabaseMetadata::ObjectStoreMap* objectStores)
     639bool IDBBackingStore::getObjectStores(int64_t databaseId, IDBDatabaseMetadata::ObjectStoreMap* objectStores)
    640640{
    641641    IDB_TRACE("IDBBackingStore::getObjectStores");
     642    if (!KeyPrefix::isValidDatabaseId(databaseId))
     643        return false;
    642644    const Vector<char> startKey = ObjectStoreMetaDataKey::encode(databaseId, 1, 0);
    643645    const Vector<char> stopKey = ObjectStoreMetaDataKey::encodeMaxKey(databaseId);
     
    725727
    726728        IDBObjectStoreMetadata metadata(objectStoreName, objectStoreId, keyPath, autoIncrement, maxIndexId);
    727         getIndexes(databaseId, objectStoreId, &metadata.indexes);
     729        if (!getIndexes(databaseId, objectStoreId, &metadata.indexes))
     730            return false;
    728731        objectStores->set(objectStoreId, metadata);
    729732    }
     733    return true;
    730734}
    731735
     
    751755{
    752756    IDB_TRACE("IDBBackingStore::createObjectStore");
     757    if (!KeyPrefix::validIds(databaseId, objectStoreId))
     758        return false;
    753759    LevelDBTransaction* levelDBTransaction = IDBBackingStore::Transaction::levelDBTransactionFrom(transaction);
    754760    if (!setMaxObjectStoreId(levelDBTransaction, databaseId, objectStoreId))
     
    780786{
    781787    IDB_TRACE("IDBBackingStore::deleteObjectStore");
     788    if (!KeyPrefix::validIds(databaseId, objectStoreId))
     789        return false;
    782790    LevelDBTransaction* levelDBTransaction = IDBBackingStore::Transaction::levelDBTransactionFrom(transaction);
    783791
     
    801809    deleteRange(levelDBTransaction, IndexMetaDataKey::encode(databaseId, objectStoreId, 0, 0), IndexMetaDataKey::encodeMaxKey(databaseId, objectStoreId));
    802810
    803     clearObjectStore(transaction, databaseId, objectStoreId);
    804     return true;
     811    return clearObjectStore(transaction, databaseId, objectStoreId);
    805812}
    806813
     
    808815{
    809816    IDB_TRACE("IDBBackingStore::getRecord");
     817    if (!KeyPrefix::validIds(databaseId, objectStoreId))
     818        return false;
    810819    LevelDBTransaction* levelDBTransaction = IDBBackingStore::Transaction::levelDBTransactionFrom(transaction);
    811820
     
    864873{
    865874    IDB_TRACE("IDBBackingStore::putRecord");
     875    if (!KeyPrefix::validIds(databaseId, objectStoreId))
     876        return false;
    866877    ASSERT(key.isValid());
     878
    867879    LevelDBTransaction* levelDBTransaction = IDBBackingStore::Transaction::levelDBTransactionFrom(transaction);
    868880    int64_t version = -1;
     
    888900}
    889901
    890 void IDBBackingStore::clearObjectStore(IDBBackingStore::Transaction* transaction, int64_t databaseId, int64_t objectStoreId)
     902bool IDBBackingStore::clearObjectStore(IDBBackingStore::Transaction* transaction, int64_t databaseId, int64_t objectStoreId)
    891903{
    892904    IDB_TRACE("IDBBackingStore::clearObjectStore");
     905    if (!KeyPrefix::validIds(databaseId, objectStoreId))
     906        return false;
    893907    LevelDBTransaction* levelDBTransaction = IDBBackingStore::Transaction::levelDBTransactionFrom(transaction);
    894     const Vector<char> startKey = KeyPrefix(databaseId, objectStoreId, 0).encode();
    895     const Vector<char> stopKey = KeyPrefix(databaseId, objectStoreId + 1, 0).encode();
     908    const Vector<char> startKey = KeyPrefix(databaseId, objectStoreId).encode();
     909    const Vector<char> stopKey = KeyPrefix(databaseId, objectStoreId + 1).encode();
    896910
    897911    deleteRange(levelDBTransaction, startKey, stopKey);
    898 }
    899 
    900 void IDBBackingStore::deleteRecord(IDBBackingStore::Transaction* transaction, int64_t databaseId, int64_t objectStoreId, const RecordIdentifier& recordIdentifier)
     912    return true;
     913}
     914
     915bool IDBBackingStore::deleteRecord(IDBBackingStore::Transaction* transaction, int64_t databaseId, int64_t objectStoreId, const RecordIdentifier& recordIdentifier)
    901916{
    902917    IDB_TRACE("IDBBackingStore::deleteRecord");
     918    if (!KeyPrefix::validIds(databaseId, objectStoreId))
     919        return false;
    903920    LevelDBTransaction* levelDBTransaction = IDBBackingStore::Transaction::levelDBTransactionFrom(transaction);
    904921
     
    908925    const Vector<char> existsEntryKey = ExistsEntryKey::encode(databaseId, objectStoreId, recordIdentifier.primaryKey());
    909926    levelDBTransaction->remove(existsEntryKey);
     927    return true;
    910928}
    911929
     
    913931bool IDBBackingStore::getKeyGeneratorCurrentNumber(IDBBackingStore::Transaction* transaction, int64_t databaseId, int64_t objectStoreId, int64_t& keyGeneratorCurrentNumber)
    914932{
     933    if (!KeyPrefix::validIds(databaseId, objectStoreId))
     934        return false;
    915935    LevelDBTransaction* levelDBTransaction = IDBBackingStore::Transaction::levelDBTransactionFrom(transaction);
    916936
     
    961981bool IDBBackingStore::maybeUpdateKeyGeneratorCurrentNumber(IDBBackingStore::Transaction* transaction, int64_t databaseId, int64_t objectStoreId, int64_t newNumber, bool checkCurrent)
    962982{
     983    if (!KeyPrefix::validIds(databaseId, objectStoreId))
     984        return false;
    963985    LevelDBTransaction* levelDBTransaction = IDBBackingStore::Transaction::levelDBTransactionFrom(transaction);
    964986
     
    9801002{
    9811003    IDB_TRACE("IDBBackingStore::keyExistsInObjectStore");
     1004    if (!KeyPrefix::validIds(databaseId, objectStoreId))
     1005        return false;
    9821006    found = false;
    9831007    LevelDBTransaction* levelDBTransaction = IDBBackingStore::Transaction::levelDBTransactionFrom(transaction);
     
    10181042
    10191043// FIXME: This should do some error handling rather than plowing ahead when bad data is encountered.
    1020 void IDBBackingStore::getIndexes(int64_t databaseId, int64_t objectStoreId, IDBObjectStoreMetadata::IndexMap* indexes)
     1044bool IDBBackingStore::getIndexes(int64_t databaseId, int64_t objectStoreId, IDBObjectStoreMetadata::IndexMap* indexes)
    10211045{
    10221046    IDB_TRACE("IDBBackingStore::getIndexes");
     1047    if (!KeyPrefix::validIds(databaseId, objectStoreId))
     1048        return false;
    10231049    const Vector<char> startKey = IndexMetaDataKey::encode(databaseId, objectStoreId, 0, 0);
    10241050    const Vector<char> stopKey = IndexMetaDataKey::encode(databaseId, objectStoreId + 1, 0, 0);
     
    10691095        indexes->set(indexId, IDBIndexMetadata(indexName, indexId, keyPath, indexUnique, indexMultiEntry));
    10701096    }
     1097    return true;
    10711098}
    10721099
     
    10961123{
    10971124    IDB_TRACE("IDBBackingStore::createIndex");
     1125    if (!KeyPrefix::validIds(databaseId, objectStoreId, indexId))
     1126        return false;
    10981127    LevelDBTransaction* levelDBTransaction = IDBBackingStore::Transaction::levelDBTransactionFrom(transaction);
    10991128    if (!setMaxIndexId(levelDBTransaction, databaseId, objectStoreId, indexId))
     
    11121141}
    11131142
    1114 void IDBBackingStore::deleteIndex(IDBBackingStore::Transaction* transaction, int64_t databaseId, int64_t objectStoreId, int64_t indexId)
     1143bool IDBBackingStore::deleteIndex(IDBBackingStore::Transaction* transaction, int64_t databaseId, int64_t objectStoreId, int64_t indexId)
    11151144{
    11161145    IDB_TRACE("IDBBackingStore::deleteIndex");
     1146    if (!KeyPrefix::validIds(databaseId, objectStoreId, indexId))
     1147        return false;
    11171148    LevelDBTransaction* levelDBTransaction = IDBBackingStore::Transaction::levelDBTransactionFrom(transaction);
    11181149
     
    11241155    const Vector<char> indexDataEnd = IndexDataKey::encodeMaxKey(databaseId, objectStoreId, indexId);
    11251156    deleteRange(levelDBTransaction, indexDataStart, indexDataEnd);
    1126 }
    1127 
    1128 void IDBBackingStore::putIndexDataForRecord(IDBBackingStore::Transaction* transaction, int64_t databaseId, int64_t objectStoreId, int64_t indexId, const IDBKey& key, const RecordIdentifier& recordIdentifier)
     1157    return true;
     1158}
     1159
     1160bool IDBBackingStore::putIndexDataForRecord(IDBBackingStore::Transaction* transaction, int64_t databaseId, int64_t objectStoreId, int64_t indexId, const IDBKey& key, const RecordIdentifier& recordIdentifier)
    11291161{
    11301162    IDB_TRACE("IDBBackingStore::putIndexDataForRecord");
    11311163    ASSERT(key.isValid());
    1132     ASSERT(indexId >= MinimumIndexId);
     1164    if (!KeyPrefix::validIds(databaseId, objectStoreId, indexId))
     1165        return false;
    11331166
    11341167    LevelDBTransaction* levelDBTransaction = IDBBackingStore::Transaction::levelDBTransactionFrom(transaction);
     
    11401173
    11411174    levelDBTransaction->put(indexDataKey, data);
     1175    return true;
    11421176}
    11431177
     
    11901224{
    11911225    IDB_TRACE("IDBBackingStore::findKeyInIndex");
     1226    ASSERT(KeyPrefix::validIds(databaseId, objectStoreId, indexId));
     1227
    11921228    ASSERT(foundEncodedPrimaryKey.isEmpty());
    11931229    found = false;
     
    12301266{
    12311267    IDB_TRACE("IDBBackingStore::getPrimaryKeyViaIndex");
     1268    if (!KeyPrefix::validIds(databaseId, objectStoreId, indexId))
     1269        return false;
    12321270
    12331271    bool found = false;
     
    12491287{
    12501288    IDB_TRACE("IDBBackingStore::keyExistsInIndex");
     1289    if (!KeyPrefix::validIds(databaseId, objectStoreId, indexId))
     1290        return false;
    12511291
    12521292    exists = false;
     
    17631803{
    17641804    ASSERT(transaction);
     1805    if (!KeyPrefix::validIds(databaseId, objectStoreId, indexId))
     1806        return false;
     1807
    17651808    bool lowerBound = range && range->lower();
    17661809    bool upperBound = range && range->upper();
  • trunk/Source/WebCore/Modules/indexeddb/IDBBackingStore.h

    r145385 r145435  
    6969    virtual bool deleteDatabase(const String& name);
    7070
    71     void getObjectStores(int64_t databaseId, IDBDatabaseMetadata::ObjectStoreMap*);
     71    bool getObjectStores(int64_t databaseId, IDBDatabaseMetadata::ObjectStoreMap*) WARN_UNUSED_RETURN;
    7272    virtual bool createObjectStore(IDBBackingStore::Transaction*, int64_t databaseId, int64_t objectStoreId, const String& name, const IDBKeyPath&, bool autoIncrement);
    7373    virtual bool deleteObjectStore(IDBBackingStore::Transaction*, int64_t databaseId, int64_t objectStoreId) WARN_UNUSED_RETURN;
     
    9090    virtual bool getRecord(IDBBackingStore::Transaction*, int64_t databaseId, int64_t objectStoreId, const IDBKey&, Vector<char>& record) WARN_UNUSED_RETURN;
    9191    virtual bool putRecord(IDBBackingStore::Transaction*, int64_t databaseId, int64_t objectStoreId, const IDBKey&, PassRefPtr<SharedBuffer> value, RecordIdentifier*) WARN_UNUSED_RETURN;
    92     virtual void clearObjectStore(IDBBackingStore::Transaction*, int64_t databaseId, int64_t objectStoreId);
    93     virtual void deleteRecord(IDBBackingStore::Transaction*, int64_t databaseId, int64_t objectStoreId, const RecordIdentifier&);
     92    virtual bool clearObjectStore(IDBBackingStore::Transaction*, int64_t databaseId, int64_t objectStoreId) WARN_UNUSED_RETURN;
     93    virtual bool deleteRecord(IDBBackingStore::Transaction*, int64_t databaseId, int64_t objectStoreId, const RecordIdentifier&) WARN_UNUSED_RETURN;
    9494    virtual bool getKeyGeneratorCurrentNumber(IDBBackingStore::Transaction*, int64_t databaseId, int64_t objectStoreId, int64_t& currentNumber) WARN_UNUSED_RETURN;
    9595    virtual bool maybeUpdateKeyGeneratorCurrentNumber(IDBBackingStore::Transaction*, int64_t databaseId, int64_t objectStoreId, int64_t newState, bool checkCurrent) WARN_UNUSED_RETURN;
    9696    virtual bool keyExistsInObjectStore(IDBBackingStore::Transaction*, int64_t databaseId, int64_t objectStoreId, const IDBKey&, RecordIdentifier* foundRecordIdentifier, bool& found) WARN_UNUSED_RETURN;
    9797
    98     virtual bool createIndex(IDBBackingStore::Transaction*, int64_t databaseId, int64_t objectStoreId, int64_t indexId, const String& name, const IDBKeyPath&, bool isUnique, bool isMultiEntry);
    99     virtual void deleteIndex(IDBBackingStore::Transaction*, int64_t databaseId, int64_t objectStoreId, int64_t indexId);
    100     virtual void putIndexDataForRecord(IDBBackingStore::Transaction*, int64_t databaseId, int64_t objectStoreId, int64_t indexId, const IDBKey&, const RecordIdentifier&);
     98    virtual bool createIndex(IDBBackingStore::Transaction*, int64_t databaseId, int64_t objectStoreId, int64_t indexId, const String& name, const IDBKeyPath&, bool isUnique, bool isMultiEntry) WARN_UNUSED_RETURN;
     99    virtual bool deleteIndex(IDBBackingStore::Transaction*, int64_t databaseId, int64_t objectStoreId, int64_t indexId) WARN_UNUSED_RETURN;
     100    virtual bool putIndexDataForRecord(IDBBackingStore::Transaction*, int64_t databaseId, int64_t objectStoreId, int64_t indexId, const IDBKey&, const RecordIdentifier&) WARN_UNUSED_RETURN;
    101101    virtual bool getPrimaryKeyViaIndex(IDBBackingStore::Transaction*, int64_t databaseId, int64_t objectStoreId, int64_t indexId, const IDBKey&, RefPtr<IDBKey>& primaryKey) WARN_UNUSED_RETURN;
    102102    virtual bool keyExistsInIndex(IDBBackingStore::Transaction*, int64_t databaseId, int64_t objectStoreId, int64_t indexId, const IDBKey& indexKey, RefPtr<IDBKey>& foundPrimaryKey, bool& exists) WARN_UNUSED_RETURN;
     
    179179private:
    180180    bool findKeyInIndex(IDBBackingStore::Transaction*, int64_t databaseId, int64_t objectStoreId, int64_t indexId, const IDBKey&, Vector<char>& foundEncodedPrimaryKey, bool& found);
    181     void getIndexes(int64_t databaseId, int64_t objectStoreId, IDBObjectStoreMetadata::IndexMap*);
     181    bool getIndexes(int64_t databaseId, int64_t objectStoreId, IDBObjectStoreMetadata::IndexMap*) WARN_UNUSED_RETURN;
    182182
    183183    String m_identifier;
  • trunk/Source/WebCore/Modules/indexeddb/IDBDatabaseBackendImpl.cpp

    r145385 r145435  
    180180class DeleteIndexOperation : public IDBTransactionBackendImpl::Operation {
    181181public:
    182     static PassOwnPtr<IDBTransactionBackendImpl::Operation> create(PassRefPtr<IDBBackingStore> backingStore, int64_t objectStoreId, int64_t indexId)
    183     {
    184         return adoptPtr(new DeleteIndexOperation(backingStore, objectStoreId, indexId));
    185     }
    186     virtual void perform(IDBTransactionBackendImpl*);
    187 private:
    188     DeleteIndexOperation(PassRefPtr<IDBBackingStore> backingStore, int64_t objectStoreId, int64_t indexId)
     182    static PassOwnPtr<IDBTransactionBackendImpl::Operation> create(PassRefPtr<IDBBackingStore> backingStore, int64_t objectStoreId, const IDBIndexMetadata& indexMetadata)
     183    {
     184        return adoptPtr(new DeleteIndexOperation(backingStore, objectStoreId, indexMetadata));
     185    }
     186    virtual void perform(IDBTransactionBackendImpl*);
     187private:
     188    DeleteIndexOperation(PassRefPtr<IDBBackingStore> backingStore, int64_t objectStoreId, const IDBIndexMetadata& indexMetadata)
    189189        : m_backingStore(backingStore)
    190190        , m_objectStoreId(objectStoreId)
    191         , m_indexId(indexId)
     191        , m_indexMetadata(indexMetadata)
    192192    {
    193193    }
     
    195195    const RefPtr<IDBBackingStore> m_backingStore;
    196196    const int64_t m_objectStoreId;
    197     const int64_t m_indexId;
     197    const IDBIndexMetadata m_indexMetadata;
    198198};
    199199
     
    535535    if (!ok)
    536536        return false;
    537     if (success) {
    538         m_backingStore->getObjectStores(m_metadata.id, &m_metadata.objectStores);
    539         return true;
    540     }
     537    if (success)
     538        return m_backingStore->getObjectStores(m_metadata.id, &m_metadata.objectStores);
     539
    541540    return m_backingStore->createIDBDatabaseMetaData(m_metadata.name, m_metadata.version, m_metadata.intVersion, m_metadata.id);
    542541}
     
    641640    const IDBIndexMetadata& indexMetadata = objectStore.indexes.get(indexId);
    642641
    643     transaction->scheduleTask(DeleteIndexOperation::create(m_backingStore, objectStoreId, indexId), DeleteIndexAbortOperation::create(this, objectStoreId, indexMetadata));
     642    transaction->scheduleTask(DeleteIndexOperation::create(m_backingStore, objectStoreId, indexMetadata), DeleteIndexAbortOperation::create(this, objectStoreId, indexMetadata));
    644643
    645644    removeIndex(objectStoreId, indexId);
     
    649648{
    650649    IDB_TRACE("DeleteIndexOperation");
    651     m_backingStore->deleteIndex(transaction->backingStoreTransaction(), transaction->database()->id(), m_objectStoreId, m_indexId);
     650    bool ok = m_backingStore->deleteIndex(transaction->backingStoreTransaction(), transaction->database()->id(), m_objectStoreId, m_indexMetadata.id);
     651    if (!ok) {
     652        RefPtr<IDBDatabaseError> error = IDBDatabaseError::create(IDBDatabaseException::UnknownError, String::format("Internal error deleting index '%s'.", m_indexMetadata.name.utf8().data()));
     653        transaction->abort(error);
     654    }
    652655}
    653656
     
    10241027    if (backingStoreCursor) {
    10251028        do {
    1026             m_backingStore->deleteRecord(transaction->backingStoreTransaction(), m_databaseId, m_objectStoreId, backingStoreCursor->recordIdentifier());
     1029            if (!m_backingStore->deleteRecord(transaction->backingStoreTransaction(), m_databaseId, m_objectStoreId, backingStoreCursor->recordIdentifier())) {
     1030                m_callbacks->onError(IDBDatabaseError::create(IDBDatabaseException::UnknownError, "Error deleting data in range"));
     1031                return;
     1032            }
    10271033        } while (backingStoreCursor->continueFunction(0));
    10281034    }
     
    10451051{
    10461052    IDB_TRACE("ObjectStoreClearOperation");
    1047     m_backingStore->clearObjectStore(transaction->backingStoreTransaction(), m_databaseId, m_objectStoreId);
     1053    if (!m_backingStore->clearObjectStore(transaction->backingStoreTransaction(), m_databaseId, m_objectStoreId)) {
     1054        m_callbacks->onError(IDBDatabaseError::create(IDBDatabaseException::UnknownError, "Error clearing object store"));
     1055        return;
     1056    }
    10481057    m_callbacks->onSuccess();
    10491058}
  • trunk/Source/WebCore/Modules/indexeddb/IDBLevelDBCoding.cpp

    r145385 r145435  
    974974}
    975975
     976KeyPrefix::KeyPrefix(int64_t databaseId)
     977    : m_databaseId(databaseId)
     978    , m_objectStoreId(0)
     979    , m_indexId(0)
     980{
     981    ASSERT(KeyPrefix::isValidDatabaseId(databaseId));
     982}
     983
     984KeyPrefix::KeyPrefix(int64_t databaseId, int64_t objectStoreId)
     985    : m_databaseId(databaseId)
     986    , m_objectStoreId(objectStoreId)
     987    , m_indexId(0)
     988{
     989    ASSERT(KeyPrefix::isValidDatabaseId(databaseId));
     990    ASSERT(KeyPrefix::isValidObjectStoreId(objectStoreId));
     991}
     992
    976993KeyPrefix::KeyPrefix(int64_t databaseId, int64_t objectStoreId, int64_t indexId)
    977994    : m_databaseId(databaseId)
     
    979996    , m_indexId(indexId)
    980997{
     998    ASSERT(KeyPrefix::isValidDatabaseId(databaseId));
     999    ASSERT(KeyPrefix::isValidObjectStoreId(objectStoreId));
     1000    ASSERT(KeyPrefix::isValidIndexId(indexId));
     1001}
     1002
     1003KeyPrefix::KeyPrefix(Type type, int64_t databaseId, int64_t objectStoreId, int64_t indexId)
     1004    : m_databaseId(databaseId)
     1005    , m_objectStoreId(objectStoreId)
     1006    , m_indexId(indexId)
     1007{
     1008    ASSERT(type == InvalidType);
     1009    ASSERT(KeyPrefix::isValidDatabaseId(databaseId));
     1010    ASSERT(KeyPrefix::isValidObjectStoreId(objectStoreId));
     1011}
     1012
     1013
     1014KeyPrefix KeyPrefix::createWithSpecialIndex(int64_t databaseId, int64_t objectStoreId, int64_t indexId)
     1015{
     1016    ASSERT(KeyPrefix::isValidDatabaseId(databaseId));
     1017    ASSERT(KeyPrefix::isValidObjectStoreId(objectStoreId));
     1018    ASSERT(indexId);
     1019    return KeyPrefix(InvalidType, databaseId, objectStoreId, indexId);
     1020}
     1021
     1022
     1023bool KeyPrefix::isValidDatabaseId(int64_t databaseId)
     1024{
     1025    return (databaseId > 0) && (databaseId < KeyPrefix::kMaxDatabaseId);
     1026}
     1027
     1028bool KeyPrefix::isValidObjectStoreId(int64_t objectStoreId)
     1029{
     1030    return (objectStoreId > 0) && (objectStoreId < KeyPrefix::kMaxObjectStoreId);
     1031}
     1032
     1033bool KeyPrefix::isValidIndexId(int64_t indexId)
     1034{
     1035    return (indexId >= MinimumIndexId) && (indexId < KeyPrefix::kMaxIndexId);
    9811036}
    9821037
     
    10051060}
    10061061
     1062Vector<char> KeyPrefix::encodeEmpty()
     1063{
     1064    const Vector<char, 4> result(4, 0);
     1065    ASSERT(encodeInternal(0, 0, 0) == Vector<char>(4, 0));
     1066    return result;
     1067}
     1068
    10071069Vector<char> KeyPrefix::encode() const
    10081070{
     
    10101072    ASSERT(m_objectStoreId != InvalidId);
    10111073    ASSERT(m_indexId != InvalidId);
    1012 
    1013     Vector<char> databaseIdString = encodeInt(m_databaseId);
    1014     Vector<char> objectStoreIdString = encodeInt(m_objectStoreId);
    1015     Vector<char> indexIdString = encodeInt(m_indexId);
    1016 
    1017     ASSERT(databaseIdString.size() <= 8);
    1018     ASSERT(objectStoreIdString.size() <= 8);
    1019     ASSERT(indexIdString.size() <= 4);
    1020 
    1021 
    1022     unsigned char firstByte = (databaseIdString.size() - 1) << 5 | (objectStoreIdString.size() - 1) << 2 | (indexIdString.size() - 1);
     1074    return encodeInternal(m_databaseId, m_objectStoreId, m_indexId);
     1075}
     1076
     1077Vector<char> KeyPrefix::encodeInternal(int64_t databaseId, int64_t objectStoreId, int64_t indexId)
     1078{
     1079    Vector<char> databaseIdString = encodeIntSafely(databaseId, kMaxDatabaseId);
     1080    Vector<char> objectStoreIdString = encodeIntSafely(objectStoreId, kMaxObjectStoreId);
     1081    Vector<char> indexIdString = encodeIntSafely(indexId, kMaxIndexId);
     1082
     1083    ASSERT(databaseIdString.size() <= kMaxDatabaseIdSizeBytes);
     1084    ASSERT(objectStoreIdString.size() <= kMaxObjectStoreIdSizeBytes);
     1085    ASSERT(indexIdString.size() <= kMaxIndexIdSizeBytes);
     1086
     1087    unsigned char firstByte = (databaseIdString.size() - 1) << (kMaxObjectStoreIdSizeBits + kMaxIndexIdSizeBits) | (objectStoreIdString.size() - 1) << kMaxIndexIdSizeBits | (indexIdString.size() - 1);
     1088    COMPILE_ASSERT(kMaxDatabaseIdSizeBits + kMaxObjectStoreIdSizeBits + kMaxIndexIdSizeBits == sizeof(firstByte) * 8, CANT_ENCODE_IDS);
    10231089    Vector<char, DefaultInlineBufferSize> ret;
    10241090    ret.append(firstByte);
     
    10691135Vector<char> SchemaVersionKey::encode()
    10701136{
    1071     KeyPrefix prefix(0, 0, 0);
    1072     Vector<char> ret = prefix.encode();
     1137    Vector<char> ret = KeyPrefix::encodeEmpty();
    10731138    ret.append(encodeByte(SchemaVersionTypeByte));
    10741139    return ret;
     
    10771142Vector<char> MaxDatabaseIdKey::encode()
    10781143{
    1079     KeyPrefix prefix(0, 0, 0);
    1080     Vector<char> ret = prefix.encode();
     1144    Vector<char> ret = KeyPrefix::encodeEmpty();
    10811145    ret.append(encodeByte(MaxDatabaseIdTypeByte));
    10821146    return ret;
     
    10851149Vector<char> DataVersionKey::encode()
    10861150{
    1087     KeyPrefix prefix(0, 0, 0);
    1088     Vector<char> ret = prefix.encode();
     1151    Vector<char> ret = KeyPrefix::encodeEmpty();
    10891152    ret.append(encodeByte(DataVersionTypeByte));
    10901153    return ret;
     
    11171180Vector<char> DatabaseFreeListKey::encode(int64_t databaseId)
    11181181{
    1119     KeyPrefix prefix(0, 0, 0);
    1120     Vector<char> ret = prefix.encode();
     1182    Vector<char> ret = KeyPrefix::encodeEmpty();
    11211183    ret.append(encodeByte(DatabaseFreeListTypeByte));
    11221184    ret.append(encodeVarInt(databaseId));
     
    11651227Vector<char> DatabaseNameKey::encode(const String& origin, const String& databaseName)
    11661228{
    1167     KeyPrefix prefix(0, 0, 0);
    1168     Vector<char> ret = prefix.encode();
     1229    Vector<char> ret = KeyPrefix::encodeEmpty();
    11691230    ret.append(encodeByte(DatabaseNameTypeByte));
    11701231    ret.append(encodeStringWithLength(origin));
     
    11931254Vector<char> DatabaseMetaDataKey::encode(int64_t databaseId, MetaDataType metaDataType)
    11941255{
    1195     KeyPrefix prefix(databaseId, 0, 0);
     1256    KeyPrefix prefix(databaseId);
    11961257    Vector<char> ret = prefix.encode();
    11971258    ret.append(encodeByte(metaDataType));
     
    12321293Vector<char> ObjectStoreMetaDataKey::encode(int64_t databaseId, int64_t objectStoreId, unsigned char metaDataType)
    12331294{
    1234     KeyPrefix prefix(databaseId, 0, 0);
     1295    KeyPrefix prefix(databaseId);
    12351296    Vector<char> ret = prefix.encode();
    12361297    ret.append(encodeByte(ObjectStoreMetaDataTypeByte));
     
    13071368Vector<char> IndexMetaDataKey::encode(int64_t databaseId, int64_t objectStoreId, int64_t indexId, unsigned char metaDataType)
    13081369{
    1309     KeyPrefix prefix(databaseId, 0, 0);
     1370    KeyPrefix prefix(databaseId);
    13101371    Vector<char> ret = prefix.encode();
    13111372    ret.append(encodeByte(IndexMetaDataTypeByte));
     
    13701431Vector<char> ObjectStoreFreeListKey::encode(int64_t databaseId, int64_t objectStoreId)
    13711432{
    1372     KeyPrefix prefix(databaseId, 0, 0);
     1433    KeyPrefix prefix(databaseId);
    13731434    Vector<char> ret = prefix.encode();
    13741435    ret.append(encodeByte(ObjectStoreFreeListTypeByte));
     
    14271488Vector<char> IndexFreeListKey::encode(int64_t databaseId, int64_t objectStoreId, int64_t indexId)
    14281489{
    1429     KeyPrefix prefix(databaseId, 0, 0);
     1490    KeyPrefix prefix(databaseId);
    14301491    Vector<char> ret = prefix.encode();
    14311492    ret.append(encodeByte(IndexFreeListTypeByte));
     
    14831544Vector<char> ObjectStoreNamesKey::encode(int64_t databaseId, const String& objectStoreName)
    14841545{
    1485     KeyPrefix prefix(databaseId, 0, 0);
     1546    KeyPrefix prefix(databaseId);
    14861547    Vector<char> ret = prefix.encode();
    14871548    ret.append(encodeByte(ObjectStoreNamesTypeByte));
     
    15261587Vector<char> IndexNamesKey::encode(int64_t databaseId, int64_t objectStoreId, const String& indexName)
    15271588{
    1528     KeyPrefix prefix(databaseId, 0, 0);
     1589    KeyPrefix prefix(databaseId);
    15291590    Vector<char> ret = prefix.encode();
    15301591    ret.append(encodeByte(IndexNamesKeyTypeByte));
     
    15581619Vector<char> ObjectStoreDataKey::encode(int64_t databaseId, int64_t objectStoreId, const Vector<char> encodedUserKey)
    15591620{
    1560     KeyPrefix prefix(databaseId, objectStoreId, SpecialIndexNumber);
     1621    KeyPrefix prefix(KeyPrefix::createWithSpecialIndex(databaseId, objectStoreId, SpecialIndexNumber));
    15611622    Vector<char> ret = prefix.encode();
    15621623    ret.append(encodedUserKey);
     
    16001661Vector<char> ExistsEntryKey::encode(int64_t databaseId, int64_t objectStoreId, const Vector<char>& encodedKey)
    16011662{
    1602     KeyPrefix prefix(databaseId, objectStoreId, SpecialIndexNumber);
     1663    KeyPrefix prefix(KeyPrefix::createWithSpecialIndex(databaseId, objectStoreId, SpecialIndexNumber));
    16031664    Vector<char> ret = prefix.encode();
    16041665    ret.append(encodedKey);
  • trunk/Source/WebCore/Modules/indexeddb/IDBLevelDBCoding.h

    r145385 r145435  
    5555bool decodeBool(const char* begin, const char* end);
    5656Vector<char> encodeInt(int64_t);
     57inline Vector<char> encodeIntSafely(int64_t nParam, size_t max)
     58{
     59    ASSERT(static_cast<size_t>(nParam) <= max);
     60    return encodeInt(nParam);
     61}
    5762int64_t decodeInt(const char* begin, const char* end);
    5863Vector<char> encodeVarInt(int64_t);
     
    7883public:
    7984    KeyPrefix();
     85    explicit KeyPrefix(int64_t databaseId);
     86    KeyPrefix(int64_t databaseId, int64_t objectStoreId);
    8087    KeyPrefix(int64_t databaseId, int64_t objectStoreId, int64_t indexId);
     88    static KeyPrefix createWithSpecialIndex(int64_t databaseId, int64_t objectStoreId, int64_t indexId);
    8189
    8290    static const char* decode(const char* start, const char* limit, KeyPrefix* result);
    8391    Vector<char> encode() const;
     92    static Vector<char> encodeEmpty();
    8493    int compare(const KeyPrefix& other) const;
    8594
     
    93102    };
    94103
     104    static const size_t kMaxDatabaseIdSizeBits = 3;
     105    static const size_t kMaxObjectStoreIdSizeBits = 3;
     106    static const size_t kMaxIndexIdSizeBits = 2;
     107
     108    static const size_t kMaxDatabaseIdSizeBytes = 1ULL << kMaxDatabaseIdSizeBits; // 8
     109    static const size_t kMaxObjectStoreIdSizeBytes = 1ULL << kMaxObjectStoreIdSizeBits; // 8
     110    static const size_t kMaxIndexIdSizeBytes = 1ULL << kMaxIndexIdSizeBits; // 4
     111
     112    static const size_t kMaxDatabaseIdBits = kMaxDatabaseIdSizeBytes * 8 - 1; // 63
     113    static const size_t kMaxObjectStoreIdBits = kMaxObjectStoreIdSizeBytes * 8 - 1; // 63
     114    static const size_t kMaxIndexIdBits = kMaxIndexIdSizeBytes * 8 - 1; // 31
     115
     116    static const int64_t kMaxDatabaseId = (1ULL << kMaxDatabaseIdBits) - 1; // max signed int64_t
     117    static const int64_t kMaxObjectStoreId = (1ULL << kMaxObjectStoreIdBits) - 1; // max signed int64_t
     118    static const int64_t kMaxIndexId = (1ULL << kMaxIndexIdBits) - 1; // max signed int32_t
     119
     120    static bool isValidDatabaseId(int64_t databaseId);
     121    static bool isValidObjectStoreId(int64_t indexId);
     122    static bool isValidIndexId(int64_t indexId);
     123    static bool validIds(int64_t databaseId, int64_t objectStoreId, int64_t indexId)
     124    {
     125        return isValidDatabaseId(databaseId) && isValidObjectStoreId(objectStoreId) && isValidIndexId(indexId);
     126    }
     127    static bool validIds(int64_t databaseId, int64_t objectStoreId)
     128    {
     129        return isValidDatabaseId(databaseId) && isValidObjectStoreId(objectStoreId);
     130    }
     131
    95132    Type type() const;
    96133
     
    100137
    101138    static const int64_t InvalidId = -1;
     139
     140private:
     141    static Vector<char> encodeInternal(int64_t databaseId, int64_t objectStoreId, int64_t indexId);
     142    // Special constructor for createWithSpecialIndex()
     143    KeyPrefix(Type, int64_t databaseId, int64_t objectStoreId, int64_t indexId);
    102144};
    103145
  • trunk/Source/WebCore/Modules/indexeddb/IDBObjectStoreBackendImpl.cpp

    r145385 r145435  
    6565    int64_t indexId = m_indexMetadata.id;
    6666    for (size_t i = 0; i < m_indexKeys.size(); ++i) {
    67         backingStore.putIndexDataForRecord(transaction, databaseId, objectStoreId, indexId, *(m_indexKeys)[i].get(), recordIdentifier);
     67        bool ok = backingStore.putIndexDataForRecord(transaction, databaseId, objectStoreId, indexId, *(m_indexKeys)[i].get(), recordIdentifier);
     68        // This should have already been verified as a valid write during verifyIndexKeys.
     69        ASSERT_UNUSED(ok, ok);
    6870    }
    6971}
  • trunk/Source/WebKit/chromium/ChangeLog

    r145406 r145435  
     12013-03-11  Alec Flett  <alecflett@chromium.org>
     2
     3        IndexedDB: Protect against key prefix overflows
     4        https://bugs.webkit.org/show_bug.cgi?id=111138
     5
     6        Reviewed by Tony Chang.
     7
     8        Add tests for invalid indexIds in basic get/put operations.
     9
    1102013-03-11  Xiyuan Xia  <xiyuan@chromium.org>
    211
  • trunk/Source/WebKit/chromium/tests/IDBBackingStoreTest.cpp

    r145385 r145435  
    2929
    3030#include "IDBFactoryBackendImpl.h"
     31#include "IDBLevelDBCoding.h"
    3132#include "SharedBuffer.h"
    3233
     
    3738
    3839using namespace WebCore;
     40using IDBLevelDBCoding::KeyPrefix;
    3941
    4042namespace {
     
    5456        const char rawValue1[] = "value1";
    5557        const char rawValue2[] = "value2";
     58        const char rawValue3[] = "value3";
    5659        m_value1.append(rawValue1, sizeof(rawValue1));
    5760        m_value2.append(rawValue2, sizeof(rawValue2));
     61        m_value3.append(rawValue3, sizeof(rawValue3));
    5862        m_key1 = IDBKey::createNumber(99);
    5963        m_key2 = IDBKey::createString("key2");
     64        m_key3 = IDBKey::createString("key3");
    6065    }
    6166
     
    6671    RefPtr<IDBKey> m_key1;
    6772    RefPtr<IDBKey> m_key2;
     73    RefPtr<IDBKey> m_key3;
    6874    Vector<char> m_value1;
    6975    Vector<char> m_value2;
     76    Vector<char> m_value3;
    7077};
    7178
     
    97104    const int64_t highDatabaseId = 1ULL << 35;
    98105    const int64_t highObjectStoreId = 1ULL << 39;
     106    // indexIds are capped at 32 bits for storage purposes.
     107    const int64_t highIndexId = 1ULL << 29;
     108
     109    const int64_t invalidHighIndexId = 1ULL << 37;
     110
     111    const RefPtr<IDBKey> indexKey = m_key2;
     112    Vector<char> indexKeyRaw = IDBLevelDBCoding::encodeIDBKey(*indexKey);
    99113    {
    100114        IDBBackingStore::Transaction transaction1(m_backingStore.get());
     
    103117        bool ok = m_backingStore->putRecord(&transaction1, highDatabaseId, highObjectStoreId, *m_key1.get(), SharedBuffer::create(m_value1.data(), m_value1.size()), &record);
    104118        EXPECT_TRUE(ok);
     119
     120        ok = m_backingStore->putIndexDataForRecord(&transaction1, highDatabaseId, highObjectStoreId, invalidHighIndexId, *indexKey, record);
     121        EXPECT_FALSE(ok);
     122
     123        ok = m_backingStore->putIndexDataForRecord(&transaction1, highDatabaseId, highObjectStoreId, highIndexId, *indexKey, record);
     124        EXPECT_TRUE(ok);
     125
    105126        ok = transaction1.commit();
    106127        EXPECT_TRUE(ok);
     
    113134        bool ok = m_backingStore->getRecord(&transaction2, highDatabaseId, highObjectStoreId, *m_key1.get(), resultValue);
    114135        EXPECT_TRUE(ok);
     136        EXPECT_EQ(m_value1, resultValue);
     137
     138        RefPtr<IDBKey> newPrimaryKey;
     139        ok = m_backingStore->getPrimaryKeyViaIndex(&transaction2, highDatabaseId, highObjectStoreId, invalidHighIndexId, *indexKey, newPrimaryKey);
     140        EXPECT_FALSE(ok);
     141
     142        ok = m_backingStore->getPrimaryKeyViaIndex(&transaction2, highDatabaseId, highObjectStoreId, highIndexId, *indexKey, newPrimaryKey);
     143        EXPECT_TRUE(ok);
     144        EXPECT_TRUE(newPrimaryKey->isEqual(m_key1.get()));
     145
    115146        ok = transaction2.commit();
    116147        EXPECT_TRUE(ok);
    117         EXPECT_EQ(m_value1, resultValue);
    118     }
     148    }
     149}
     150
     151// Make sure that other invalid ids do not crash.
     152TEST_F(IDBBackingStoreTest, InvalidIds)
     153{
     154    // valid ids for use when testing invalid ids
     155    const int64_t databaseId = 1;
     156    const int64_t objectStoreId = 1;
     157    const int64_t indexId = IDBLevelDBCoding::MinimumIndexId;
     158    const int64_t invalidLowIndexId = 19; // indexIds must be > IDBLevelDBCoding::MinimumIndexId
     159
     160    const RefPtr<SharedBuffer> value = SharedBuffer::create(m_value1.data(), m_value1.size());
     161    Vector<char> resultValue;
     162
     163    IDBBackingStore::Transaction transaction1(m_backingStore.get());
     164    transaction1.begin();
     165
     166    IDBBackingStore::RecordIdentifier record;
     167    bool ok = m_backingStore->putRecord(&transaction1, databaseId, KeyPrefix::InvalidId, *m_key1.get(), value, &record);
     168    EXPECT_FALSE(ok);
     169    ok = m_backingStore->putRecord(&transaction1, databaseId, 0, *m_key1.get(), value, &record);
     170    EXPECT_FALSE(ok);
     171    ok = m_backingStore->putRecord(&transaction1, KeyPrefix::InvalidId, objectStoreId, *m_key1.get(), value, &record);
     172    EXPECT_FALSE(ok);
     173    ok = m_backingStore->putRecord(&transaction1, 0, objectStoreId, *m_key1.get(), value, &record);
     174    EXPECT_FALSE(ok);
     175
     176    ok = m_backingStore->getRecord(&transaction1, databaseId, KeyPrefix::InvalidId, *m_key1.get(), resultValue);
     177    EXPECT_FALSE(ok);
     178    ok = m_backingStore->getRecord(&transaction1, databaseId, 0, *m_key1.get(), resultValue);
     179    EXPECT_FALSE(ok);
     180    ok = m_backingStore->getRecord(&transaction1, KeyPrefix::InvalidId, objectStoreId, *m_key1.get(), resultValue);
     181    EXPECT_FALSE(ok);
     182    ok = m_backingStore->getRecord(&transaction1, 0, objectStoreId, *m_key1.get(), resultValue);
     183    EXPECT_FALSE(ok);
     184
     185    RefPtr<IDBKey> newPrimaryKey;
     186    ok = m_backingStore->getPrimaryKeyViaIndex(&transaction1, databaseId, objectStoreId, KeyPrefix::InvalidId, *m_key1, newPrimaryKey);
     187    EXPECT_FALSE(ok);
     188    ok = m_backingStore->getPrimaryKeyViaIndex(&transaction1, databaseId, objectStoreId, invalidLowIndexId, *m_key1, newPrimaryKey);
     189    EXPECT_FALSE(ok);
     190    ok = m_backingStore->getPrimaryKeyViaIndex(&transaction1, databaseId, objectStoreId, 0, *m_key1, newPrimaryKey);
     191    EXPECT_FALSE(ok);
     192
     193    ok = m_backingStore->getPrimaryKeyViaIndex(&transaction1, KeyPrefix::InvalidId, objectStoreId, indexId, *m_key1, newPrimaryKey);
     194    EXPECT_FALSE(ok);
     195    ok = m_backingStore->getPrimaryKeyViaIndex(&transaction1, databaseId, KeyPrefix::InvalidId, indexId, *m_key1, newPrimaryKey);
     196    EXPECT_FALSE(ok);
    119197}
    120198
     
    167245        EXPECT_EQ(databaseId, database.id);
    168246
    169         m_backingStore->getObjectStores(database.id, &database.objectStores);
     247        ok = m_backingStore->getObjectStores(database.id, &database.objectStores);
     248        EXPECT_TRUE(ok);
    170249
    171250        EXPECT_EQ(1, database.objectStores.size());
  • trunk/Source/WebKit/chromium/tests/IDBFakeBackingStore.h

    r145385 r145435  
    4242    virtual bool createObjectStore(Transaction*, int64_t databaseId, int64_t objectStoreId, const String& name, const IDBKeyPath&, bool autoIncrement) OVERRIDE { return false; };
    4343
    44     virtual void clearObjectStore(Transaction*, int64_t databaseId, int64_t objectStoreId) OVERRIDE { }
    45     virtual void deleteRecord(Transaction*, int64_t databaseId, int64_t objectStoreId, const RecordIdentifier&) OVERRIDE { }
     44    virtual bool clearObjectStore(Transaction*, int64_t databaseId, int64_t objectStoreId) OVERRIDE { return false; }
     45    virtual bool deleteRecord(Transaction*, int64_t databaseId, int64_t objectStoreId, const RecordIdentifier&) OVERRIDE { return false; }
    4646    virtual bool getKeyGeneratorCurrentNumber(Transaction*, int64_t databaseId, int64_t objectStoreId, int64_t& currentNumber) OVERRIDE { return true; }
    4747    virtual bool maybeUpdateKeyGeneratorCurrentNumber(Transaction*, int64_t databaseId, int64_t objectStoreId, int64_t newNumber, bool checkCurrent) OVERRIDE { return true; }
     
    4949
    5050    virtual bool createIndex(Transaction*, int64_t databaseId, int64_t objectStoreId, int64_t indexId, const String& name, const IDBKeyPath&, bool isUnique, bool isMultiEntry) OVERRIDE { return false; };
    51     virtual void deleteIndex(Transaction*, int64_t databaseId, int64_t objectStoreId, int64_t indexId) OVERRIDE { }
    52     virtual void putIndexDataForRecord(Transaction*, int64_t databaseId, int64_t objectStoreId, int64_t indexId, const IDBKey&, const RecordIdentifier&) OVERRIDE { }
     51    virtual bool deleteIndex(Transaction*, int64_t databaseId, int64_t objectStoreId, int64_t indexId) OVERRIDE { return false; }
     52    virtual bool putIndexDataForRecord(Transaction*, int64_t databaseId, int64_t objectStoreId, int64_t indexId, const IDBKey&, const RecordIdentifier&) OVERRIDE { return false; }
    5353
    5454    virtual PassRefPtr<Cursor> openObjectStoreKeyCursor(Transaction*, int64_t databaseId, int64_t objectStoreId, const IDBKeyRange*, IndexedDB::CursorDirection) OVERRIDE { return PassRefPtr<Cursor>(); }
  • trunk/Source/WebKit/chromium/tests/IDBLevelDBCodingTest.cpp

    r143347 r145435  
    102102    }
    103103}
    104    
     104
    105105TEST(IDBLevelDBCodingTest, EncodeBool)
    106106{
     
    712712    keys.append(IndexDataKey::encode(1, 1, 31, minIDBKey(), minIDBKey(), 0));
    713713    keys.append(IndexDataKey::encode(1, 2, 30, minIDBKey(), minIDBKey(), 0));
    714     keys.append(IndexDataKey::encodeMaxKey(1, 2, INT32_MAX));
     714    keys.append(IndexDataKey::encodeMaxKey(1, 2, INT32_MAX - 1));
    715715
    716716    for (size_t i = 0; i < keys.size(); ++i) {
Note: See TracChangeset for help on using the changeset viewer.