Guard native patch targets and document verified WebRTC backport preparation

This commit is contained in:
Karl committed 2026-10-14 01:49:32 +02:00
1 parent 09bf616d98
commit 7925633527
3 files changed
+241

No files matched your search

+103
View File
@@ -177,6 +177,109 @@ and Node pins remain unchanged. Dawn, DevTools, ICU and Ink checkouts are presen
V8 and WebRTC dependency checkouts are still absent. Free disk at this checkpoint
was246.2GiB. This is a resumable preparation checkpoint, not a completed build.
A subsequent bounded resume has checked out WebRTC at
`6f37672d358475cd17544121a12494da454d85fb` and V8 at
`fedd021b538607860f9c56e5c193ad8dcd4f2bb1`, each verified against its own Git
root. A read-only snapshot found all nineteen required patch targets present
and tracked-clean, with no patch imports yet. The post-import verifier's real
negative control rejected all nineteen unpatched targets as expected. Ignored
evidence: `artifacts/native-build/patch-readiness.json` and
`patch-import-negative-control.json`. At this checkpoint synchronization is
still active on VK-GL-CTS and WebGPU CTS; this was not full dependency completion,
patch applicability, compilation or native acceptance.
That same resume subsequently terminated with exit1 (`stopped:false`): Windows
antivirus rejected CIPD package `chromium/third_party/updater/chrome_win_x86`,
instance `NuXBEfKu8082d55WUbs6_3P0puTPCI4A1Df4BnQ1Z40C`, during fetch/install.
This is a measured security-tool rejection, not a timeout or a completed sync.
No antivirus exception, disabling, bypass or package retry was performed.
Compilation remains unverified. The existing Git sources can be independently
rechecked for the targeted Python patch import, which neither installs that
package nor proves complete dependency readiness.
The subsequent official patch import used normal host Python after the isolated
embedded interpreter failed before entering patch code. The official import
then terminated with exit1: the excluded `fontconfig` directory existed without
its own Git root, so its target fell back to Chromium during parallel import.
Its Git-Am invocation failed against Chromium's active import and overwrote
Chromium's upstream patch references. Chromium has272 commits relative to the
saved pristine baseline and no remaining Git-Am directory, but its overwritten
references count only268 commits. This discrepancy is a real failed gate, not
an accepted successful import. Preserve the original log and verification
failure; verify the complete ordered series against the saved baseline before
considering an explicit, evidence-backed reference repair. No reset, abort,
patch replay or automatic reference repair has been performed at this checkpoint.
The ordered-series verification subsequently proved all272 Chromium patches and
all eighteen other required repositories. Each of the thirteen modified parent
Gitlinks was checked against its registered, independently verified child's
actual HEAD, own Git root and stage0 mode160000 entry; arbitrary source changes
remain rejected. Only the two corrupted upstream references remained invalid.
An explicit atomic compare-and-swap repair then restored both references to
`2c592105bbcd9490a9894df48d0fe59b2c512651`, requiring their observed old value
`619d23e60466caaa2b5f35e16f15a255b5c09ffa`. Chromium HEAD stayed
`b33e21917a1ce686ca6134585cdaca88eaadf9f6`. A first transaction failed due to CRLF
stdin; a synthetic/no-op protocol check proved literal LF bytes necessary, and
the corrected start/prepare/commit transaction exited0. Original failures and
both transaction journals remain retained. No patch replay, source reset or
dependency staging occurred. The fresh post-repair verifier then passed all
nineteen required targets with the thirteen explicitly matched Gitlinks, and
`git apply --check` for the backport exited0 against Electron-patched Chromium.
Evidence: `artifacts/native-build/patch-import-verification-post-cas.json` and
the retained recovery journal. This proves combined source applicability, not
a compiled or runtime-verified fix.
The verified backport was then applied with `git apply --index` and committed as
`6f8b80fef13bfdc9d9c70df567ebcad722d70467`, parent
`b33e21917a1ce686ca6134585cdaca88eaadf9f6`, in the ignored native source checkout.
Exactly the sixteen expected files were staged; no dependency Gitlinks were
included. Thirteen structural source assertions passed, the index is clean and
both official upstream references remain unchanged. The single-commit mailbox
`artifacts/native-build/backport-applied.patch` has SHA256
`070ece6ffbd5723aa8bb0c7e94f855c931be2b2277a6e69b5908cabb5881343b`;
`backport-applied-journal.json` records provenance and exact files. This source
checkpoint remains **uncompiled and runtime-unverified**. The shipped project
runtime is unchanged; source assertions cannot substitute for the native gates.
### Patch readiness before compilation
ATL/MFC is required for the native toolchain, not for the Python patch import
or a combined-source text check. After dependency synchronization is terminal,
verify each required Windows repository's own Git root, pinned revision and clean
working tree; `git -C <directory> rev-parse HEAD` alone can accidentally resolve
the parent Chromium repository when a dependency has not yet checked out.
The pinned official DEPS hook invokes the following action from
`artifacts/native-build/electron44`:
```powershell
python3 src/electron/script/apply_all_patches.py src/electron/patches/config.json
git -C src apply --check C:/Users/karl/Safetynet/docs/native-runtime/chromium-152-webrtc-binder-backport.patch
```
This identifies the upstream action; **do not rerun its unfiltered twenty-target
config on Windows** after the measured ancestor collision. For a fresh checkout,
first run `scripts/native-patch-preflight.mjs`, explicitly verify the Linux-only
fontconfig exclusion, and use a reviewed config containing only the nineteen
required targets with the same official Python importer. Never replay imports
over an already patched tree. The hook does not invoke compilation.
Its exit code is insufficient evidence: missing repositories are skipped with
warnings. Verify imports and upstream references for each required target in
`patches/config.json`. In this pinned checkout, nineteen targets are required:
despite their names, `squirrel.mac` and `ReactiveObjC` are synchronized under
`process_deps`, without a macOS condition. Only `fontconfig` is excluded by its
Chromium `checkout_linux` condition. Do not infer exclusions from directory names.
Save each repository's pre-import HEAD. Both the legacy and checkout-specific
upstream references must resolve to that commit, and it must be an ancestor of
the resulting HEAD. Compare imported commit counts and ordered subjects with
the repository's `.patches` list. Reference existence alone is insufficient:
the importer writes references before `git am`, which can subsequently fail.
Do not blindly rerun the hook after partial failure or reset conflicting hunks.
Seven existing Chromium patches touch backport paths, so the earlier pristine
check does not prove compatibility with Electron's patches. Full `runhooks`, GN,
compilation and native transport acceptance remain separate gates. The backport
addresses denial stability; it does not provide WebRTC content inspection.
### Missing ATL/MFC: explicit administrator step
Inspection found the existing installer at
+87
View File
@@ -0,0 +1,87 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import {execFile} from 'node:child_process';
import {promisify} from 'node:util';
import {fileURLToPath} from 'node:url';
const execute=promisify(execFile);
const FONTCONFIG='src/third_party/fontconfig/src';
function inside(root,target){const relative=path.relative(root,target);return relative===''||(!relative.startsWith(`..${path.sep}`)&&relative!=='..'&&!path.isAbsolute(relative));}
async function contained(root,target){
const resolved=await fs.realpath(target);
if(!inside(root,resolved))throw Error('path-outside-checkout');
return resolved;
}
async function nearestContained(root,target){
if(!inside(root,target))throw Error('path-outside-checkout');
let current=target;
while(true){try{return await contained(root,current);}catch(error){if(error.code!=='ENOENT')throw error;const parent=path.dirname(current);if(parent===current)throw error;current=parent;}}
}
async function git(repo,args){
const {stdout}=await execute('git',['--no-optional-locks','-C',repo,...args],{env:{...process.env,GIT_OPTIONAL_LOCKS:'0'},timeout:120000,maxBuffer:8*1024*1024,windowsHide:true});
return stdout.trim();
}
// Read-only prerequisite check. In particular, an existing directory is not
// proof of an independent checkout: Git silently searches ancestor repositories.
export async function nativePatchPreflight({checkoutRoot,configPath,excludeWindowsFontconfig=false,platform=process.platform}){
const result={scope:'Read-only patch target preflight; not patch import or build proof.',passed:false,required:[],excluded:[],errors:[]};
try{
const root=await fs.realpath(checkoutRoot);result.checkoutRoot=root;
const config=await contained(root,path.resolve(configPath));
const targets=JSON.parse(await fs.readFile(config,'utf8'));
if(!Array.isArray(targets)||targets.length===0||targets.length>64)throw Error('invalid-patch-config');
const seen=new Set();
for(const target of targets){
const row={repo:target?.repo,patchDir:target?.patch_dir,errors:[]};
try{
if(!target||typeof target.repo!=='string'||typeof target.patch_dir!=='string'||!target.repo||!target.patch_dir||path.isAbsolute(target.repo)||path.isAbsolute(target.patch_dir)||seen.has(target.repo))throw Error('invalid-patch-target');
seen.add(target.repo);
const repo=path.resolve(root,target.repo),patchDir=path.resolve(root,target.patch_dir);
await nearestContained(root,repo);
const patches=await contained(root,patchDir),list=await contained(root,path.join(patches,'.patches'));
// Electron's official reader keeps every list line, including blanks.
const text=await fs.readFile(list,'utf8');const names=text.split(/\r?\n/);if(names.at(-1)==='')names.pop();
if(!names.length||names.some(name=>!name||path.isAbsolute(name)))throw Error('invalid-patch-list');
for(const name of names){const file=await contained(root,path.resolve(patches,name));if(!inside(patches,file))throw Error('patch-outside-patch-directory');}
row.patchCount=names.length;
if(target.repo===FONTCONFIG&&excludeWindowsFontconfig){
if(platform!=='win32')throw Error('fontconfig-exclusion-requires-windows');
const deps=await fs.readFile(await contained(root,path.join(root,'src/DEPS')),'utf8');
if(!/['"]src\/third_party\/fontconfig\/src['"]\s*:\s*\{[^}]*['"]condition['"]\s*:\s*['"]checkout_linux['"]/s.test(deps))throw Error('fontconfig-exclusion-condition-unverified');
row.reason='Explicit Windows exclusion: Chromium DEPS checkout_linux';result.excluded.push(row);continue;
}
const own=await contained(root,repo);
try{await contained(root,path.join(own,'.git'));}catch(error){if(error.code==='ENOENT')throw Error('missing-own-git-marker',{cause:error});throw error;}
const top=await git(own,['rev-parse','--show-toplevel']);
if(await fs.realpath(top)!==own)throw Error('ancestor-git-root');
const gitDir=await git(own,['rev-parse','--absolute-git-dir']);await contained(root,gitDir);
const am=path.resolve(own,await git(own,['rev-parse','--git-path','rebase-apply']));
await nearestContained(root,am);
try{await fs.stat(am);throw Error('git-am-in-progress');}catch(error){if(error.code!=='ENOENT')throw error;}
row.head=await git(own,['rev-parse','--verify','HEAD']);
row.trackedClean=(await git(own,['status','--porcelain=v1','--untracked-files=no']))==='';
if(!row.trackedClean)throw Error('tracked-worktree-not-clean');
}catch(error){row.errors.push(error.code==='ENOENT'?'missing-required-path':error.message||'preflight-failed');}
result.required.push(row);
}
result.errors=result.required.filter(row=>row.errors.length).map(row=>({repo:row.repo,errors:row.errors}));
result.passed=result.errors.length===0;
}catch(error){result.errors.push({errors:[error.code==='ENOENT'?'missing-required-path':error.message||'preflight-failed']});}
result.requiredCount=result.required.length;result.excludedCount=result.excluded.length;
return result;
}
if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)){
const args=process.argv.slice(2);const allowed=new Set(['--checkout-root','--config','--exclude-windows-fontconfig']);let checkoutRoot,configPath,excludeWindowsFontconfig=false;
try{
for(let i=0;i<args.length;i++){
if(!allowed.has(args[i]))throw Error('invalid-cli-argument');
if(args[i]==='--exclude-windows-fontconfig'){excludeWindowsFontconfig=true;continue;}
const value=args[++i];if(!value||value.startsWith('--'))throw Error('missing-cli-value');
if(args[i-1]==='--checkout-root')checkoutRoot=value;else configPath=value;
}
if(!checkoutRoot||!configPath)throw Error('checkout-root-and-config-required');
const result=await nativePatchPreflight({checkoutRoot,configPath,excludeWindowsFontconfig});console.log(JSON.stringify(result,null,2));process.exitCode=result.passed?0:1;
}catch(error){console.log(JSON.stringify({passed:false,errors:[error.message]}));process.exitCode=1;}
}
+51
View File
@@ -0,0 +1,51 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import {execFile} from 'node:child_process';
import {promisify} from 'node:util';
import {nativePatchPreflight} from '../scripts/native-patch-preflight.mjs';
const exec=promisify(execFile);
async function fixture(t){
const root=await fs.mkdtemp(path.join(os.tmpdir(),'safetynet-native-preflight-'));t.after(()=>fs.rm(root,{recursive:true,force:true}));
const config=path.join(root,'config.json'),patchDir=path.join(root,'patches');await fs.mkdir(patchDir);await fs.writeFile(path.join(patchDir,'.patches'),'one.patch\n');await fs.writeFile(path.join(patchDir,'one.patch'),'synthetic');
await fs.writeFile(config,JSON.stringify([{repo:'src',patch_dir:'patches'}]));
return {root,config,options:{checkoutRoot:root,configPath:config}};
}
async function init(repo){await fs.mkdir(repo,{recursive:true});await exec('git',['init',repo]);await fs.writeFile(path.join(repo,'tracked.txt'),'baseline');await exec('git',['-C',repo,'add','tracked.txt']);await exec('git',['-C',repo,'-c','user.name=Fixture','-c','[email protected]','-c','commit.gpgsign=false','commit','-m','baseline']);}
test('accepts an independent clean checkout and leaves HEAD unchanged',async t=>{
const f=await fixture(t),repo=path.join(f.root,'src');await init(repo);const before=(await exec('git',['-C',repo,'rev-parse','HEAD'])).stdout.trim();const r=await nativePatchPreflight(f.options);assert.equal(r.passed,true);assert.equal(r.required[0].head,before);assert.equal(r.required[0].patchCount,1);assert.equal((await exec('git',['-C',repo,'rev-parse','HEAD'])).stdout.trim(),before);
});
test('rejects existing child directory whose Git resolves to ancestor',async t=>{
const f=await fixture(t);await init(f.root);await fs.mkdir(path.join(f.root,'src'));const r=await nativePatchPreflight(f.options);assert.equal(r.passed,false);assert.deepEqual(r.errors[0].errors,['missing-own-git-marker']);
});
test('missing required target fails even when patch directory exists',async t=>{
const f=await fixture(t);const r=await nativePatchPreflight(f.options);assert.equal(r.passed,false);assert.equal(r.requiredCount,1);assert.deepEqual(r.errors[0].errors,['missing-required-path']);
});
test('rejects changed tracked files',async t=>{
const f=await fixture(t);await init(path.join(f.root,'src'));await fs.writeFile(path.join(f.root,'src/tracked.txt'),'changed');const r=await nativePatchPreflight(f.options);assert.equal(r.passed,false);assert.deepEqual(r.errors[0].errors,['tracked-worktree-not-clean']);
});
test('rejects residual git-am state despite clean tracked files',async t=>{
const f=await fixture(t),repo=path.join(f.root,'src');await init(repo);await fs.mkdir(path.join(repo,'.git/rebase-apply'));const r=await nativePatchPreflight(f.options);assert.equal(r.passed,false);assert.deepEqual(r.errors[0].errors,['git-am-in-progress']);assert.equal((await exec('git',['-C',repo,'status','--porcelain=v1','--untracked-files=no'])).stdout.trim(),'');
});
test('rejects checkout symlink or junction escaping supplied root',async t=>{
const f=await fixture(t),outside=await fs.mkdtemp(path.join(os.tmpdir(),'safetynet-preflight-outside-'));t.after(()=>fs.rm(outside,{recursive:true,force:true}));await init(outside);await fs.symlink(outside,path.join(f.root,'src'),process.platform==='win32'?'junction':'dir');const r=await nativePatchPreflight(f.options);assert.equal(r.passed,false);assert.deepEqual(r.errors[0].errors,['path-outside-checkout']);
});
test('rejects escaping config and missing listed patch',async t=>{
const f=await fixture(t);await init(path.join(f.root,'src'));await fs.rm(path.join(f.root,'patches/one.patch'));assert.equal((await nativePatchPreflight(f.options)).passed,false);const r=await nativePatchPreflight({...f.options,configPath:path.join(f.root,'../outside.json')});assert.equal(r.passed,false);
});
test('fontconfig exclusion is opt-in, Windows-only, exact path and DEPS condition checked',async t=>{
const f=await fixture(t);await init(path.join(f.root,'src'));await fs.mkdir(path.join(f.root,'src/third_party/fontconfig/src'),{recursive:true});await fs.writeFile(path.join(f.root,'src/DEPS'),"deps = {'src/third_party/fontconfig/src': {'url':'fixture','condition':'checkout_linux'}}");await fs.writeFile(f.config,JSON.stringify([{repo:'src/third_party/fontconfig/src',patch_dir:'patches'}]));
assert.equal((await nativePatchPreflight(f.options)).passed,false);
const options={...f.options,excludeWindowsFontconfig:true,platform:'win32'};const good=await nativePatchPreflight(options);assert.equal(good.passed,true);assert.equal(good.excludedCount,1);assert.equal(good.requiredCount,0);
assert.equal((await nativePatchPreflight({...options,platform:'linux'})).passed,false);
await fs.writeFile(path.join(f.root,'src/DEPS'),"deps={'src/third_party/fontconfig/src':{'condition':'checkout_win'}}");assert.equal((await nativePatchPreflight(options)).passed,false);
});
test('rejects listed patch traversal outside its patch directory',async t=>{
const f=await fixture(t);await init(path.join(f.root,'src'));await fs.writeFile(path.join(f.root,'other.patch'),'synthetic');await fs.writeFile(path.join(f.root,'patches/.patches'),'../other.patch\n');const r=await nativePatchPreflight(f.options);assert.equal(r.passed,false);assert.deepEqual(r.errors[0].errors,['patch-outside-patch-directory']);
});
test('CLI requires explicit roots and returns failing status',async()=>{
await assert.rejects(exec(process.execPath,['scripts/native-patch-preflight.mjs']),error=>error.code===1&&JSON.parse(error.stdout).passed===false);
});