Files
safetynet/apps/network-broker/wfp-topology.cjs
T

61 lines
4.3 KiB
JavaScript

'use strict';
// Contract only; not an installer and not proof of kernel enforcement.
// https://learn.microsoft.com/en-us/windows/win32/fwp/filter-arbitration
const {GUIDS,planBrowserNetworkBoundary}=require('./windows-wfp-plan.cjs');
const OWNED_PRIORITY=65535;
const RELEVANT_LAYERS=Object.freeze([GUIDS.connectV4,GUIDS.connectV6,GUIDS.recvV4,GUIDS.recvV6]);
const USER_ID='af043a0a-b34d-4f86-979c-c90371af6e66';
function planServiceReceiver({appId,brokerPort,serviceSid}={}){
const base=planBrowserNetworkBoundary({appId,brokerPort});
if(typeof serviceSid!=='string'||!/^S-1-5-80-(?:\d+-){4}\d+$/.test(serviceSid)||serviceSid.split('-').slice(4).some(x=>String(Number(x))!==x||Number(x)>4294967295))throw new TypeError('Actual SCM service SID required');
const tuple=Object.freeze([
{field:GUIDS.protocol,match:'equal',type:'FWP_UINT8',value:6},
{field:GUIDS.localAddress,match:'equal',type:'FWP_UINT32',value:0x7f000001},
{field:GUIDS.localPort,match:'equal',type:'FWP_UINT16',value:brokerPort}
].map(Object.freeze));
// Higher normal PERMIT skips the lower BLOCK in OUR sublayer. No clear-action-
// right: corporate blocks in other sublayers retain their veto.
const filters=Object.freeze([
Object.freeze({id:'broker-service-receiver',layer:GUIDS.recvV4,weight:2,action:'FWP_ACTION_PERMIT',clearActionRight:false,conditions:Object.freeze([...tuple,Object.freeze({field:GUIDS.appId,match:'equal',type:'FWP_BYTE_BLOB_TYPE',value:base.applicationId}),Object.freeze({field:USER_ID,match:'equal',type:'FWP_SECURITY_DESCRIPTOR_TYPE',value:`D:(A;;CC;;;${serviceSid})`})])}),
Object.freeze({id:'broker-receiver-deny',layer:GUIDS.recvV4,weight:1,action:'FWP_ACTION_BLOCK',clearActionRight:false,conditions:tuple})
]);
return Object.freeze({activation:'not-installed',sublayerPriority:OWNED_PRIORITY,filters,serviceSid,requiresActualServiceToken:true,completeBoundary:false});
}
function validateTopology({ownedSublayer,sublayers,filters,enumerationComplete,allObjectsReadable}={}){
const fail=reason=>Object.freeze({safe:false,reason});
if(enumerationComplete!==true||allObjectsReadable!==true)return fail('topology-incomplete');
if(typeof ownedSublayer!=='string'||!Array.isArray(sublayers)||!Array.isArray(filters)||sublayers.length>4096||filters.length>65536)return fail('topology-invalid');
const map=new Map();
for(const s of sublayers){if(!s||typeof s.key!=='string'||map.has(s.key)||!Number.isInteger(s.weight)||s.weight<0||s.weight>65535)return fail('topology-invalid');map.set(s.key,s.weight);}
if(map.get(ownedSublayer)!==OWNED_PRIORITY)return fail('owned-priority-invalid');
let ownedCount=0;const ownedIds=new Set();
for(const f of filters){
if(!f||typeof f!=='object'||typeof f.layer!=='string'||typeof f.sublayer!=='string')return fail('snapshot-invalid');
if(!RELEVANT_LAYERS.includes(f.layer))continue;
if(!map.has(f.sublayer))return fail('unknown-sublayer');
if(f.sublayer!==ownedSublayer&&map.get(f.sublayer)>=OWNED_PRIORITY)return fail('priority-collision');
// Our complete filter readback must additionally match exact trusted plans.
if(f.sublayer===ownedSublayer){
if(f.ownedVerified!==true||typeof f.key!=='string'||ownedIds.has(f.key))return fail('owned-filter-unverified');
ownedIds.add(f.key);ownedCount++;
}
}
if(ownedCount!==8)return fail('owned-filter-count');
return Object.freeze({safe:true,reason:'topology-compatible',zeroChangeWindowProven:false});
}
function createTopologyGuard({quarantine}={}){
if(typeof quarantine!=='function')throw new TypeError('Quarantine callback required');
let state='unverified';
function invalidate(reason){if(state==='quarantined')return;state='quarantined';quarantine(reason);}
return Object.freeze({
admit(snapshot){if(state!=='unverified')return false;const result=validateTopology(snapshot);if(!result.safe){invalidate(result.reason);return false;}state='verified';return true;},
// Subscribe ALL filter/sublayer/BFE state changes before snapshot, on a
// separate read-only session. Invalidate synchronously on any notification;
// never re-admit automatically after a change or failed subscription.
changed(){invalidate('wfp-topology-changed');},
monitoringFailed(){invalidate('wfp-monitor-unavailable');},
current(){return state==='verified';},status(){return state;}
});
}
module.exports={OWNED_PRIORITY,RELEVANT_LAYERS,planServiceReceiver,validateTopology,createTopologyGuard};