Ignore:
Timestamp:
May 10, 2022, 2:55:45 PM (3 years ago)
Author:
[email protected]
Message:

Add optional Integrity checks at JSC API boundaries.
https://bugs.webkit.org/show_bug.cgi?id=240264

Reviewed by Yusuke Suzuki.

  1. Defined ENABLE_EXTRA_INTEGRITY_CHECKS in Integrity.h. JSC developers can enable this for their local build if they want to enable more prolific Integrity audits. This is disabled by default.

This feature is currently only supported for USE(JSVALUE64) targets.

The following changes only take effect if ENABLE(EXTRA_INTEGRITY_CHECKS) is enabled.
Otherwise, these are no-ops.

  1. Added Integrity audits to all toJS and toRef conversion functions in APICast.h. This will help us detect if bad values are passed across the API boundary.
  1. Added some Integrity audits in JSValue.mm where the APICast ones were insufficient.

The following changes are in effect even when ENABLE(EXTRA_INTEGRITY_CHECKS) is
disabled. Some of these were made to support ENABLE(EXTRA_INTEGRITY_CHECKS), and
some are just clean up in related code that I had to touch along the way.

  1. Moved isSanePointer() to Integrity.h so that it can be used in more places.
  1. Changed VM registration with the VMInspector so that it's registered earlier and removed later. Integrity audits may need to audit VM pointers while the VM is being constructed and destructed.
  1. Added VM::m_isInService to track when the VM is fully constructed or about to be destructed since the VM is now registered with the VMInspector differently (see (4) above). Applied this check in places that need it.
  1. Fixed VMInspector::isValidExecutableMemory() to check the ExecutableAllocator directly without iterating VMs (which is completely unnecessary).
  1. Fixed VMInspector::isValidExecutableMemory() and VMInspector::codeBlockForMachinePC() to use AdoptLock. This fixes a race condition where the lock can be contended after ensureIsSafeToLock() succeeds.
  1. Added VMInspector::isValidVM() to check if a VM pointer is registered or not. VMInspector caches the most recently added or found VM so that isValidVM() can just check the cache for its fast path.
  1. Moved the implementation of VMInspector::verifyCell() to Integrity::analyzeCell()

and add more checks to it. VMInspector::verifyCell() now calls Integrity::verifyCell()
which uses Integrity::analyzeCell() to do the real cell analysis.

  1. Also strengten Integrity::auditStructureID() so that it will check if a

Structure's memory has been released. This change is enabled on Debug builds
by default as well as when ENABLE(EXTRA_INTEGRITY_CHECKS). It is disabled
on Release builds.

  • API/APICast.h:

(toJS):
(toJSForGC):
(uncheckedToJS):
(toRef):
(toGlobalRef):

  • API/JSContext.mm:
  • API/JSContextRef.cpp:
  • API/JSScript.mm:
  • API/JSValue.mm:

(ObjcContainerConvertor::convert):
(objectToValueWithoutCopy):
(objectToValue):

  • API/JSVirtualMachine.mm:
  • API/JSWeakPrivate.cpp:
  • API/glib/JSCContext.cpp:
  • API/glib/JSCWrapperMap.cpp:
  • API/tests/JSObjectGetProxyTargetTest.cpp:
  • bytecode/SpeculatedType.cpp:

(JSC::speculationFromCell):
(JSC::isSanePointer): Deleted.

  • heap/HeapFinalizerCallback.cpp:
  • heap/WeakSet.h:
  • runtime/Structure.h:
  • runtime/VM.cpp:

(JSC::VM::VM):
(JSC::VM::~VM):

  • runtime/VM.h:

(JSC::VM::isInService const):

  • tools/HeapVerifier.cpp:

(JSC::HeapVerifier::checkIfRecorded):

  • tools/Integrity.cpp:

(JSC::Integrity::Random::reloadAndCheckShouldAuditSlow):
(JSC::Integrity::auditCellMinimallySlow):
(JSC::Integrity::doAudit):
(JSC::Integrity::Analyzer::analyzeVM):
(JSC::Integrity::Analyzer::analyzeCell):
(JSC::Integrity::doAuditSlow):
(JSC::Integrity::verifyCell):
(): Deleted.
(JSC::Integrity::auditCellFully): Deleted.

  • tools/Integrity.h:

(JSC::isSanePointer):
(JSC::Integrity::auditCell):
(JSC::Integrity::audit):

  • tools/IntegrityInlines.h:

(JSC::Integrity::auditCell):
(JSC::Integrity::auditCellFully):
(JSC::Integrity::auditStructureID):
(JSC::Integrity::doAudit):

  • tools/VMInspector.cpp:

(JSC::VMInspector::add):
(JSC::VMInspector::remove):
(JSC::VMInspector::isValidVMSlow):
(JSC::VMInspector::dumpVMs):
(JSC::VMInspector::isValidExecutableMemory):
(JSC::VMInspector::codeBlockForMachinePC):
(JSC::ensureIsSafeToLock): Deleted.

  • tools/VMInspector.h:

(JSC::VMInspector::isValidVM):
(): Deleted.
(JSC::VMInspector::unusedVerifier): Deleted.

  • tools/VMInspectorInlines.h:

(JSC::VMInspector::verifyCell):
(JSC::VMInspector::verifyCellSize): Deleted.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/tools/Integrity.cpp

    r288815 r294017  
    11/*
    2  * Copyright (C) 2019-2021 Apple Inc. All rights reserved.
     2 * Copyright (C) 2019-2022 Apple Inc. All rights reserved.
    33 *
    44 * Redistribution and use in source and binary forms, with or without
     
    2727#include "Integrity.h"
    2828
     29#include "APICast.h"
     30#include "CellSize.h"
     31#include "IntegrityInlines.h"
     32#include "JSCast.h"
    2933#include "JSCellInlines.h"
     34#include "JSGlobalObject.h"
    3035#include "Options.h"
    3136#include "VMInspectorInlines.h"
     
    3439namespace Integrity {
    3540
    36 namespace {
    37 constexpr bool verbose = false;
     41namespace IntegrityInternal {
     42static constexpr bool verbose = false;
    3843}
    3944
     
    4954    if (!Options::randomIntegrityAuditRate()) {
    5055        m_triggerBits = 0; // Never trigger, and don't bother reloading.
    51         if (verbose)
     56        if (IntegrityInternal::verbose)
    5257            dataLogLn("disabled Integrity audits: trigger bits ", RawPointer(reinterpret_cast<void*>(m_triggerBits)));
    5358        return false;
     
    6267        m_triggerBits = m_triggerBits | (static_cast<uint64_t>(trigger) << i);
    6368    }
    64     if (verbose)
     69    if (IntegrityInternal::verbose)
    6570        dataLogLn("reloaded Integrity trigger bits ", RawPointer(reinterpret_cast<void*>(m_triggerBits)));
    6671    ASSERT(m_triggerBits >= (1ull << 63));
     
    6873}
    6974
    70 void auditCellFully(VM& vm, JSCell* cell)
    71 {
    72     VMInspector::verifyCell<VMInspector::ReleaseAssert>(vm, cell);
    73 }
    74 
    7575void auditCellMinimallySlow(VM&, JSCell* cell)
    7676{
    7777    if (Gigacage::contains(cell)) {
    7878        if (cell->type() != JSImmutableButterflyType) {
    79             if (verbose)
    80                 dataLogLn("Bad cell ", RawPointer(cell), " ", JSValue(cell));
     79            if (IntegrityInternal::verbose)
     80                dataLogLn("Integrity ERROR: Bad cell ", RawPointer(cell), " ", JSValue(cell));
    8181            CRASH();
    8282        }
     
    8484}
    8585
     86#if USE(JSVALUE64)
     87
     88JSContextRef doAudit(JSContextRef ctx)
     89{
     90    IA_ASSERT(ctx, "NULL JSContextRef");
     91    toJS(ctx); // toJS will trigger an audit.
     92    return ctx;
     93}
     94
     95JSGlobalContextRef doAudit(JSGlobalContextRef ctx)
     96{
     97    IA_ASSERT(ctx, "NULL JSGlobalContextRef");
     98    toJS(ctx); // toJS will trigger an audit.
     99    return ctx;
     100}
     101
     102JSObjectRef doAudit(JSObjectRef objectRef)
     103{
     104    if (!objectRef)
     105        return objectRef;
     106    toJS(objectRef); // toJS will trigger an audit.
     107    return objectRef;
     108}
     109
     110JSValueRef doAudit(JSValueRef valueRef)
     111{
     112#if CPU(ADDRESS64)
     113    if (!valueRef)
     114        return valueRef;
     115    toJS(valueRef); // toJS will trigger an audit.
     116#endif
     117    return valueRef;
     118}
     119
     120JSValue doAudit(JSValue value)
     121{
     122    if (value.isCell())
     123        doAudit(value.asCell());
     124    return value;
     125}
     126
     127bool Analyzer::analyzeVM(VM& vm, Analyzer::Action action)
     128{
     129    IA_ASSERT_WITH_ACTION(VMInspector::isValidVM(&vm), {
     130        VMInspector::dumpVMs();
     131        if (action == Action::LogAndCrash)
     132            RELEASE_ASSERT(VMInspector::isValidVM(&vm));
     133        else
     134            return false;
     135    }, "Invalid VM %p", &vm);
     136    return true;
     137}
     138
     139#if COMPILER(MSVC) || !VA_OPT_SUPPORTED
     140
     141#define AUDIT_VERIFY(cond, format, ...) do { \
     142        IA_ASSERT_WITH_ACTION(cond, { \
     143            WTFLogAlways("    cell %p", cell); \
     144            if (action == Action::LogAndCrash) \
     145                RELEASE_ASSERT((cond), ##__VA_ARGS__); \
     146            else \
     147                return false; \
     148        }, format, ##__VA_ARGS__); \
     149    } while (false)
     150
     151#else // not (COMPILER(MSVC) || !VA_OPT_SUPPORTED)
     152
     153#define AUDIT_VERIFY(cond, format, ...) do { \
     154        IA_ASSERT_WITH_ACTION(cond, { \
     155            WTFLogAlways("    cell %p", cell); \
     156            if (action == Action::LogAndCrash) \
     157                RELEASE_ASSERT((cond) __VA_OPT__(,) __VA_ARGS__); \
     158            else \
     159                return false; \
     160        }, format __VA_OPT__(,) __VA_ARGS__); \
     161    } while (false)
     162
     163#endif // COMPILER(MSVC) || !VA_OPT_SUPPORTED
     164
     165bool Analyzer::analyzeCell(VM& vm, JSCell* cell, Analyzer::Action action)
     166{
     167    AUDIT_VERIFY(isSanePointer(cell), "cell %p cell.type %d", cell, cell->type());
     168
     169    size_t allocatorCellSize = 0;
     170    if (cell->isPreciseAllocation()) {
     171        PreciseAllocation& preciseAllocation = cell->preciseAllocation();
     172        AUDIT_VERIFY(&preciseAllocation.vm() == &vm,
     173            "cell %p cell.type %d preciseAllocation.vm %p vm %p", cell, cell->type(), &preciseAllocation.vm(), &vm);
     174
     175        bool isValidPreciseAllocation = false;
     176        for (auto* i : vm.heap.objectSpace().preciseAllocations()) {
     177            if (i == &preciseAllocation) {
     178                isValidPreciseAllocation = true;
     179                break;
     180            }
     181        }
     182        AUDIT_VERIFY(isValidPreciseAllocation, "cell %p cell.type %d", cell, cell->type());
     183
     184        allocatorCellSize = preciseAllocation.cellSize();
     185    } else {
     186        MarkedBlock& block = cell->markedBlock();
     187        MarkedBlock::Handle& blockHandle = block.handle();
     188        AUDIT_VERIFY(&block.vm() == &vm,
     189            "cell %p cell.type %d markedBlock.vm %p vm %p", cell, cell->type(), &block.vm(), &vm);
     190
     191        uintptr_t blockStartAddress = reinterpret_cast<uintptr_t>(blockHandle.start());
     192        AUDIT_VERIFY(blockHandle.contains(cell),
     193            "cell %p cell.type %d markedBlock.start %p markedBlock.end %p", cell, cell->type(), blockHandle.start(), blockHandle.end());
     194
     195        uintptr_t cellAddress = reinterpret_cast<uintptr_t>(cell);
     196        uintptr_t cellOffset = cellAddress - blockStartAddress;
     197        allocatorCellSize = block.cellSize();
     198        bool cellIsProperlyAligned = !(cellOffset % allocatorCellSize);
     199        AUDIT_VERIFY(cellIsProperlyAligned,
     200            "cell %p cell.type %d allocator.cellSize %zu", cell, cell->type(), allocatorCellSize);
     201    }
     202
     203    JSType cellType = cell->type();
     204    if (cell->type() != JSImmutableButterflyType)
     205        AUDIT_VERIFY(!Gigacage::contains(cell), "cell %p cell.type %d", cell, cellType);
     206
     207    WeakSet& weakSet = cell->cellContainer().weakSet();
     208    AUDIT_VERIFY(!weakSet.m_allocator || isSanePointer(weakSet.m_allocator),
     209        "cell %p cell.type %d weakSet.allocator %p", cell, cell->type(), weakSet.m_allocator);
     210    AUDIT_VERIFY(!weakSet.m_nextAllocator || isSanePointer(weakSet.m_nextAllocator),
     211        "cell %p cell.type %d weakSet.allocator %p", cell, cell->type(), weakSet.m_nextAllocator);
     212
     213    // If we're currently destructing the cell, then we can't rely on its
     214    // structure being good. Skip the following tests which rely on structure.
     215    if (vm.currentlyDestructingCallbackObject == cell)
     216        return true;
     217
     218    auto structureID = cell->structureID();
     219
     220    Structure* structure = structureID.tryDecode();
     221    AUDIT_VERIFY(structure,
     222        "cell %p cell.type %d structureID.bits 0x%x", cell, cellType, structureID.bits());
     223    if (action == Analyzer::Action::LogAndCrash) {
     224        // structure should be pointing to readable memory. Force a read.
     225        WTF::opaque(*bitwise_cast<uintptr_t*>(structure));
     226    }
     227
     228    const ClassInfo* classInfo = structure->classInfoForCells();
     229    AUDIT_VERIFY(cellType == structure->m_blob.type(),
     230        "cell %p cell.type %d structureBlob.type %d", cell, cellType, structure->m_blob.type());
     231
     232    size_t size = cellSize(cell);
     233    AUDIT_VERIFY(size <= allocatorCellSize,
     234        "cell %p cell.type %d cell.size %zu allocator.cellSize %zu, classInfo.cellSize %u", cell, cellType, size, allocatorCellSize, classInfo->staticClassSize);
     235    if (isDynamicallySizedType(cellType)) {
     236        AUDIT_VERIFY(size >= classInfo->staticClassSize,
     237            "cell %p cell.type %d cell.size %zu classInfo.cellSize %u", cell, cellType, size, classInfo->staticClassSize);
     238    }
     239
     240    if (cell->isObject()) {
     241        AUDIT_VERIFY(jsDynamicCast<JSObject*>(cell),
     242            "cell %p cell.type %d", cell, cell->type());
     243
     244        if (Gigacage::isEnabled(Gigacage::JSValue)) {
     245            JSObject* object = bitwise_cast<JSObject*>(cell);
     246            const Butterfly* butterfly = object->butterfly();
     247            AUDIT_VERIFY(!butterfly || Gigacage::isCaged(Gigacage::JSValue, butterfly),
     248                "cell %p cell.type %d butterfly %p", cell, cell->type(), butterfly);
     249        }
     250    }
     251
     252    return true;
     253}
     254
     255bool Analyzer::analyzeCell(JSCell* cell, Analyzer::Action action)
     256{
     257    if (!cell)
     258        return cell;
     259
     260    JSValue value = JSValue::decode(static_cast<EncodedJSValue>(bitwise_cast<uintptr_t>(cell)));
     261    AUDIT_VERIFY(value.isCell(), "Invalid cell address: cell %p", cell);
     262
     263    VM& vm = cell->vm();
     264    analyzeVM(vm, action);
     265    return analyzeCell(vm, cell, action);
     266}
     267
     268#undef AUDIT_VERIFY
     269
     270VM* doAuditSlow(VM* vm)
     271{
     272    Analyzer::analyzeVM(*vm, Analyzer::Action::LogAndCrash);
     273    return vm;
     274}
     275
     276JSCell* doAudit(JSCell* cell)
     277{
     278    Analyzer::analyzeCell(cell, Analyzer::Action::LogAndCrash);
     279    return cell;
     280}
     281
     282JSCell* doAudit(VM& vm, JSCell* cell)
     283{
     284    if (!cell)
     285        return cell;
     286    Analyzer::analyzeCell(vm, cell, Analyzer::Action::LogAndCrash);
     287    return cell;
     288}
     289
     290bool verifyCell(JSCell* cell)
     291{
     292    bool valid = Analyzer::analyzeCell(cell, Analyzer::Action::LogOnly);
     293    WTFLogAlways("Cell %p is %s", cell, valid ? "VALID" : "INVALID");
     294    return valid;
     295}
     296
     297bool verifyCell(VM& vm, JSCell* cell)
     298{
     299    bool valid = Analyzer::analyzeCell(vm, cell, Analyzer::Action::LogOnly);
     300    WTFLogAlways("Cell %p is %s", cell, valid ? "VALID" : "INVALID");
     301    return valid;
     302}
     303
     304JSObject* doAudit(JSObject* object)
     305{
     306    if (!object)
     307        return object;
     308    JSCell* cell = doAudit(reinterpret_cast<JSCell*>(object));
     309    IA_ASSERT(cell->isObject(), "Invalid JSObject %p", object);
     310    return object;
     311}
     312
     313JSGlobalObject* doAudit(JSGlobalObject* globalObject)
     314{
     315    doAudit(reinterpret_cast<JSCell*>(globalObject));
     316    IA_ASSERT(globalObject->isGlobalObject(), "Invalid JSGlobalObject %p", globalObject);
     317    return globalObject;
     318}
     319
     320#endif // USE(JSVALUE64)
     321
    86322} // namespace Integrity
    87323} // namespace JSC
Note: See TracChangeset for help on using the changeset viewer.