Skip to content

[vulnerability][acl] ACL is skipped for super classes/objects #336

Description

@t2ym

[vulnerability][acl] ACL is skipped for super classes/objects

Root Cause

  • Chained super classes and objects are not tracked for ACL except for new C() and super()

Important Notes for Fix

  • Defined ACL policies for global objects/classes must also guard chained global objects/classes properly since Policy.defaultAcl() is applied only to untracked objects and classes
  • With Policy.globalAcl(), many formerly unrecognized global objects may be found and their access should be denied. ALL 3rd PARTY APIs NEED THEIR DEDICATED ACLs.
  • With Policy.globalAcl() and Policy.defaultAcl(), tracking the prototype chain is stopped once a named ACL (acl['name']) is found and applied in order to avoid redundant application of chained ACLs.

Reproducible Code Example

  • There are other ways of circumventing ACL for chained classes/objects
{
    class FakeClass { };
    let fakeObject = new FakeClass();
    Object.setPrototypeOf(Object.getPrototypeOf(fakeObject), window);
    // still (fakeObject instanceof FakeClass) === true
    Reflect.get(fakeObject, 'caches'); // Illegal invocation; not Permission Denied
}

Fix

  • Policy.defaultAcl() ACL callback function
    • Applied to
      • acl[S_DEFAULT][S_DEFAULT] for
        • Non-global objects
        • Instances of non-global classes
      • acl.Function[S_PROTOTYPE][S_INSTANCE][S_DEFAULT] for
        • Non-global functions
    • Tracks
      • 'r' (read) operations -> track chain if necessary
        • this.hasOwnProperty('property') -> access allowed
        • super.hasOwnProperty('property') -> applyAcl(super, 'property')`
          • Stop tracking the chain if a named ACL is found and applied
        • !Reflect.has(this, 'property') -> property undefined -> access allowed
      • 'w' (write) operations -> access allowed as the operation is for this
        • this.hasOwnProperty('property') -> access allowed
        • super.hasOwnProperty('property')
          • setter -> access allowed for this object
            • TODO: Assuming setter is harmless to the super class. Really?
          • value -> access allowed for this object's own property, which is harmless to super
      • 'x' (execute) operations -> track chain if necessary
        • this.hasOwnProperty('property') -> access allowed
        • super.hasOwnProperty('property') -> applyAcl(super, 'property')`
          • Stop tracking the chain if a named ACL is found and applied
        • !Reflect.has(this, 'property') -> property undefined -> access allowed
        • In addition, _globalMethods.get(this.property) -> applyAcl(this.property)
      • 'R' (get descriptor) -> access allowed for this object
      • 'W' (define property) -> access allowed for this object
      • S_ALL property -> track chain -> applyAcl(super, S_ALL)
        • Stop tracking the chain if a named ACL is found and applied
    • TODO: Handling primitives properly
      • To be handled in a separate issue
  • Policy.globalAcl() ACL callback function for global objects with no dedicated ACL
    • Replacing Policy.avoidGlobalClone()
      • Note: Many formerly unrecognized global objects may be found and their access should be denied by Policy.globalAcl(). ALL 3rd PARTY APIs NEED THEIR DEDICATED ACLs.
      • The difference between Policy.globalAcl() and Policy.avoidGlobalClone() is similar to that between strict and targeted policy in SELinux
        • Policy.globalAcl() does not allow writing to global objects unless dedicated ACLs are defined
        • Policy.avoidGlobalClone() allow writing to no-dedicated ACL global objects if they are not clones of other global objects. Targeted objects with dedicated ACLs can deny writing access to them.
    • Applied to
      • acl[S_GLOBAL][S_DEFAULT] for
        • Global objects with no dedicated ACLs
      • acl[S_GLOBAL][S_PROTOTYPE][S_DEFAULT] for
        • Instances of global classes with no dedicated ACLs
      • acl[mainGlobalObjectName][S_DEFAULT] for
        • Global properties with no dedicatd ACLs in acl[mainGlobalObjectName]
          • TODO: When to apply acl[mainGlobalObjectName][S_DEFAULT] is to be cleared, as it is currently ambiguous.
    • Tracks
      • 'r' (read) operations -> track chain if necessary
        • this.hasOwnProperty('property') -> access allowed
        • super.hasOwnProperty('property') -> applyAcl(super, 'property')`
          • Stop tracking the chain if a named ACL is found and applied
        • !Reflect.has(this, 'property') -> property undefined -> access allowed
      • 'w' (write) operations -> instance access allowed; class access denied
        • static properties of this no-acl global object -> access denied
        • an instance of this no-acl global object -> access allowed
        • the prototype object of this no-acl global object -> access denied
      • 'x' (execute) operations -> track chain if necessary
        • this.hasOwnProperty('property') -> access allowed
        • super.hasOwnProperty('property') -> applyAcl(super, 'property')`
          • Stop tracking the chain if a named ACL is found and applied
        • !Reflect.has(this, 'property') -> property undefined -> access allowed
        • In addition, _globalMethods.get(this.property) -> applyAcl(this.property)
      • 'R' (get descriptor) -> instance access allowed; class access denied
        • static properties of this no-acl global object -> access denied
        • an instance of this no-acl global object -> access allowed
        • the prototype object of this no-acl global object -> access denied
      • 'W' (define property) -> instance access allowed; class access denied
        • static properties of this no-acl global object -> access denied
        • an instance of this no-acl global object -> access allowed
        • the prototype object of this no-acl global object -> access denied
      • S_ALL property -> track chain -> applyAcl(super, S_ALL)
        • Stop tracking the chain if a named ACL is found and applied
  • Policy.patternAcl({ r, w : (name, prop) => true, x, R, W }) ACL callback function
    • Check the name and property of the target for targeted opType
    • Policy.globalAcl() is applied for other opTypes
    • Use Case:
      • Libraries to define global variables with dynamic names
        • Example:
          • firebase-auth.js defines window.closure_lm_{random number} property
          • '@firebase_auth_closure_global_variable_writer': Policy.patternAcl({ w: (name, prop) => name === 'window' && typeof prop === 'string' && prop.startsWith('closure_') })
            • applied to
              • acl[S_GLOBAL]['@firebase_auth_closure_global_variable_writer']
              • acl[mainGlobalObjectName]['@firebase_auth_closure_global_variable_writer']
  • detectName(target) utility function
    • Check _globalObjects.get(target)
    • Then, detects constructor
    • TODO: Handling primitives properly
      • To be handled in a separate issue
  • Update acl.Window[S_PROTOTYPE]
    • Define acl.Window[S_PROTOTYPE][S_INSTANCE] and chain it to acl
  • Slight update for hookBenchmark() to work with the current version
  • TODO: Performance Optimization
    • As these changes introduce performance overheads, more performance optimization is required
    • __hook__acl as the default callback
      • __hook__acl is highly recommended over __hook__ with more overheads
    • Remove redundant operations
    • Caching

Issues

Note: The normalizedArgs argument used for an object property collection normalizedArgs.result = [name, ...] Array object, which conveys the real object name that denied access, is created as an array literal ([element0, element1,...]) on each __hook__ call and is discarded just after the call to __hook__ returns and onThrow handles the exception from the permission error. Therefore, the normalizedArgs.result = [name, ...] object on each denied __hook__ call DOES NOT HAVE ANY SIDE EFFECTS on its following processes.

  • Delete normalizedArgs.result = [name, ...] property just in case
    • Status: Fixed in 0.4.0-alpha.10
    • Note: SRI (subresource integrity) in Chrome has a tricky bug that fails to verify certain seemingly unpredictable hash values, whose workaround is to insert a syntactically meaningless whitespace in a comment of the target file whose integrity value cannot be verified. In this failure case in no-hook-authorization.js after the fix for the used result property, a space character is appended in a comment, which is effective.
  • Compatibility with 0.4.0-alpha.8
    • Status: Under investigation
    • As ACL has become more comprehensive and more rigid, there can be some incompatibility issues
      • USER FEEDBACKS ARE CRITICAL TO SUCH ISSUES even if I try hard to keep maximum compatibility.

Notes for planning

  • I am going to call the current policy as "policy-v0"
  • "policy-v1" with completely new design will be sought
    • No milestones for now since removing vulnerabilities has higher priority than performance

#70 Benchmarks with hookBenchmark()

Chrome version hook . (op/s) = (op/s) () (op/s) f (op/s)
80.0.3987.87 0.4.0a8 acl 1,645,278 1,638,269 1,173,020 3,874,467
80.0.3987.87 0.4.0a* min 11,782,726 11,113,580 6,385,288 8,086,689
80.0.3987.87 0.4.0a* acl 1,173,158 1,198,897 858,737 4,006,410
80.0.3987.87 0.4.0a* 633,874 642,797 519,912 2,023,062
80.0.3987.106 0.4.0a9 acl 1,137,009 1,109,877 775,674 3,661,662
80.0.3987.106 0.4.0a13 acl 1,105,460 1,102,171 741,179 3,687,315

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions