Skip to content

Commit 0ede223

Browse files
bfarias-godaddyaddaleax
authored andcommitted
policy: add startup benchmark and make SRI lazier
PR-URL: #29527 Reviewed-By: Joyee Cheung <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 7be8dde commit 0ede223

File tree

4 files changed

+119
-33
lines changed

4 files changed

+119
-33
lines changed

‎benchmark/policy/policy-startup.js‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Tests the impact on eager operations required for policies affecting
2+
// general startup, does not test lazy operations
3+
'use strict';
4+
constcommon=require('../common.js');
5+
6+
constconfigs={
7+
n: [1024]
8+
};
9+
10+
constoptions={
11+
flags: ['--expose-internals']
12+
};
13+
14+
constbench=common.createBenchmark(main,configs,options);
15+
16+
functionmain(conf){
17+
consthash=(str,algo)=>{
18+
consthash=require('crypto').createHash(algo);
19+
returnhash.update(str).digest('base64');
20+
};
21+
constresources=Object.fromEntries(
22+
// Simulate graph of 1k modules
23+
Array.from({length: 1024},(_,i)=>{
24+
return[`./_${i}`,{
25+
integrity: `sha256-${hash(`// ./_${i}`,'sha256')}`,
26+
dependencies: Object.fromEntries(Array.from({
27+
// Average 3 deps per 4 modules
28+
length: Math.floor((i%4)/2)
29+
},(_,ii)=>{
30+
return[`_${ii}`,`./_${i-ii}`];
31+
})),
32+
}];
33+
})
34+
);
35+
constjson=JSON.parse(JSON.stringify({ resources }),(_,o)=>{
36+
if(o&&typeofo==='object'){
37+
Reflect.setPrototypeOf(o,null);
38+
Object.freeze(o);
39+
}
40+
returno;
41+
});
42+
const{ Manifest }=require('internal/policy/manifest');
43+
44+
bench.start();
45+
46+
for(leti=0;i<conf.n;i++){
47+
newManifest(json,'file://benchmark/policy-relative');
48+
}
49+
50+
bench.end(conf.n);
51+
}

‎lib/internal/policy/manifest.js‎

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const{
44
ArrayIsArray,
55
Map,
66
MapPrototypeSet,
7+
ObjectCreate,
78
ObjectEntries,
89
ObjectFreeze,
910
ObjectSetPrototypeOf,
@@ -29,7 +30,6 @@ const{URL } = require('internal/url');
2930
const{ createHash, timingSafeEqual }=crypto;
3031
constHashUpdate=uncurryThis(crypto.Hash.prototype.update);
3132
constHashDigest=uncurryThis(crypto.Hash.prototype.digest);
32-
constBufferEquals=uncurryThis(Buffer.prototype.equals);
3333
constBufferToString=uncurryThis(Buffer.prototype.toString);
3434
constkRelativeURLStringPattern=/^\.{0,2}\//;
3535
const{ getOptionValue }=require('internal/options');
@@ -54,9 +54,47 @@ function REACTION_LOG(error){
5454
}
5555

5656
classManifest{
57+
/**
58+
* @type{Map<string, true | string | SRI[]>}
59+
*
60+
* Used to compare a resource to the content body at the resource.
61+
* `true` is used to signify that all integrities are allowed, otherwise,
62+
* SRI strings are parsed to compare with the body.
63+
*
64+
* This stores strings instead of eagerly parsing SRI strings
65+
* and only converts them to SRI data structures when needed.
66+
* This avoids needing to parse all SRI strings at startup even
67+
* if some never end up being used.
68+
*/
5769
#integrities =newSafeMap();
70+
/**
71+
* @type{Map<string, (specifier: string) => true | URL>}
72+
*
73+
* Used to find where a dependency is located.
74+
*
75+
* This stores functions to lazily calculate locations as needed.
76+
* `true` is used to signify that the location is not specified
77+
* by the manifest and default resolution should be allowed.
78+
*/
5879
#dependencies =newSafeMap();
80+
/**
81+
* @type{(err: Error) => void}
82+
*
83+
* Performs default action for what happens when a manifest encounters
84+
* a violation such as abort()ing or exiting the process, throwing the error,
85+
* or logging the error.
86+
*/
5987
#reaction =null;
88+
/**
89+
* `obj` should match the policy file format described in the docs
90+
* it is expected to not have prototype pollution issues either by reassigning
91+
* the prototype to `null` for values or by running prior to any user code.
92+
*
93+
* `manifestURL` is a URL to resolve relative locations against.
94+
*
95+
* @param{object} obj
96+
* @param{string} manifestURL
97+
*/
6098
constructor(obj,manifestURL){
6199
constintegrities=this.#integrities;
62100
constdependencies=this.#dependencies;
@@ -96,35 +134,14 @@ class Manifest{
96134
letintegrity=manifestEntries[i][1].integrity;
97135
if(!integrity)integrity=null;
98136
if(integrity!=null){
99-
debug(`Manifest contains integrity for url ${originalHREF}`);
137+
debug('Manifest contains integrity for url %s',originalHREF);
100138
if(typeofintegrity==='string'){
101-
constsri=ObjectFreeze(SRI.parse(integrity));
102139
if(integrities.has(resourceHREF)){
103-
constold=integrities.get(resourceHREF);
104-
letmismatch=false;
105-
106-
if(old.length!==sri.length){
107-
mismatch=true;
108-
}else{
109-
compare:
110-
for(letsriI=0;sriI<sri.length;sriI++){
111-
for(letoldI=0;oldI<old.length;oldI++){
112-
if(sri[sriI].algorithm===old[oldI].algorithm&&
113-
BufferEquals(sri[sriI].value,old[oldI].value)&&
114-
sri[sriI].options===old[oldI].options){
115-
continue compare;
116-
}
117-
}
118-
mismatch=true;
119-
break compare;
120-
}
121-
}
122-
123-
if(mismatch){
140+
if(integrities.get(resourceHREF)!==integrity){
124141
thrownewERR_MANIFEST_INTEGRITY_MISMATCH(resourceURL);
125142
}
126143
}
127-
integrities.set(resourceHREF,sri);
144+
integrities.set(resourceHREF,integrity);
128145
}elseif(integrity===true){
129146
integrities.set(resourceHREF,true);
130147
}else{
@@ -136,7 +153,7 @@ class Manifest{
136153

137154
letdependencyMap=manifestEntries[i][1].dependencies;
138155
if(dependencyMap===null||dependencyMap===undefined){
139-
dependencyMap={};
156+
dependencyMap=ObjectCreate(null);
140157
}
141158
if(typeofdependencyMap==='object'&&!ArrayIsArray(dependencyMap)){
142159
/**
@@ -198,13 +215,18 @@ class Manifest{
198215

199216
assertIntegrity(url,content){
200217
consthref=`${url}`;
201-
debug(`Checking integrity of ${href}`);
218+
debug('Checking integrity of %s',href);
202219
constintegrities=this.#integrities;
203220
constrealIntegrities=newMap();
204221

205222
if(integrities.has(href)){
206-
constintegrityEntries=integrities.get(href);
223+
letintegrityEntries=integrities.get(href);
207224
if(integrityEntries===true)returntrue;
225+
if(typeofintegrityEntries==='string'){
226+
constsri=ObjectFreeze(SRI.parse(integrityEntries));
227+
integrities.set(href,sri);
228+
integrityEntries=sri;
229+
}
208230
// Avoid clobbered Symbol.iterator
209231
for(leti=0;i<integrityEntries.length;i++){
210232
const{

‎lib/internal/policy/sri.js‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
'use strict';
2-
// Value of https://w3c.github.io/webappsec-subresource-integrity/#the-integrity-attribute
2+
// Utility to parse the value of
3+
// https://w3c.github.io/webappsec-subresource-integrity/#the-integrity-attribute
34

45
const{
56
ObjectDefineProperty,
67
ObjectFreeze,
8+
ObjectGetPrototypeOf,
79
ObjectSeal,
10+
ObjectSetPrototypeOf,
811
RegExp,
912
RegExpPrototypeExec,
1013
RegExpPrototypeTest,
1114
StringPrototypeSlice,
1215
}=primordials;
1316

14-
// Returns [{algorithm, value (in base64 string), options,}]
1517
const{
1618
ERR_SRI_PARSE
1719
}=require('internal/errors').codes;
@@ -21,17 +23,19 @@ const kHASH_ALGO = 'sha(?:256|384|512)'
2123
// Base64
2224
constkHASH_VALUE='[A-Za-z0-9+/]+[=]{0,2}';
2325
constkHASH_EXPRESSION=`(${kHASH_ALGO})-(${kHASH_VALUE})`;
24-
constkOPTION_EXPRESSION=`(${kVCHAR}*)`;
26+
// Ungrouped since unused
27+
constkOPTION_EXPRESSION=`(?:${kVCHAR}*)`;
2528
constkHASH_WITH_OPTIONS=`${kHASH_EXPRESSION}(?:[?](${kOPTION_EXPRESSION}))?`;
2629
constkSRIPattern=RegExp(`(${kWSP}*)(?:${kHASH_WITH_OPTIONS})`,'g');
2730
ObjectSeal(kSRIPattern);
2831
constkAllWSP=RegExp(`^${kWSP}*$`);
2932
ObjectSeal(kAllWSP);
3033

3134
constBufferFrom=require('buffer').Buffer.from;
35+
constRealArrayPrototype=ObjectGetPrototypeOf([]);
3236

37+
// Returns{algorithm, value (in base64 string), options,}[]
3338
constparse=(str)=>{
34-
kSRIPattern.lastIndex=0;
3539
letprevIndex=0;
3640
letmatch;
3741
constentries=[];
@@ -62,7 +66,7 @@ const parse = (str) =>{
6266
thrownewERR_SRI_PARSE(str,str.charAt(prevIndex),prevIndex);
6367
}
6468
}
65-
returnentries;
69+
returnObjectSetPrototypeOf(entries,RealArrayPrototype);
6670
};
6771

6872
module.exports={
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use strict';
2+
3+
require('../common');
4+
5+
construnBenchmark=require('../common/benchmark');
6+
7+
runBenchmark('policy',[
8+
'n=1',
9+
]);

0 commit comments

Comments
(0)