Skip to content

Commit a33101d

Browse files
committed
fix: improve pack parsing and validation
1 parent 62c6a23 commit a33101d

File tree

1 file changed

+38
-8
lines changed

1 file changed

+38
-8
lines changed

src/proxy/processors/push-action/parsePush.ts

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ async function exec(req: any, action: Action): Promise<Action> {
2828
action.addStep(step);
2929
return action;
3030
}
31-
const packetLines = parsePacketLines(req.body);
31+
const [packetLines, packDataOffset] = parsePacketLines(req.body);
32+
3233
const refUpdates = packetLines.filter((line) => line.includes('refs/heads/'));
3334

3435
if (refUpdates.length !== 1) {
@@ -45,8 +46,24 @@ async function exec(req: any, action: Action): Promise<Action> {
4546
action.branch = ref.replace(/\0.*/, '').trim();
4647
action.setCommit(oldCommit, newCommit);
4748

48-
const index = req.body.lastIndexOf('PACK');
49-
const buf = req.body.slice(index);
49+
// Check if the offset is valid and if there's data after it
50+
if (packDataOffset >= req.body.length) {
51+
step.log('No PACK data found after packet lines.');
52+
step.setError('Your push has been blocked. PACK data is missing.');
53+
action.addStep(step);
54+
return action;
55+
}
56+
57+
const buf = req.body.slice(packDataOffset);
58+
59+
// Verify that data actually starts with PACK signature
60+
if (buf.length < 4 || buf.toString('utf8', 0, 4) !== 'PACK') {
61+
step.log(`Expected PACK signature at offset ${packDataOffset}, but found something else.`);
62+
step.setError('Your push has been blocked. Invalid PACK data structure.');
63+
action.addStep(step);
64+
return action;
65+
}
66+
5067
const [meta, contentBuff] = getPackMeta(buf);
5168
const contents = getContents(contentBuff as any, meta.entries as number);
5269

@@ -324,27 +341,39 @@ const unpack = (buf: Buffer) => {
324341

325342
/**
326343
* Parses the packet lines from a buffer into an array of strings.
344+
* Also returns the offset immediately following the parsed lines (including the flush packet).
327345
* @param {Buffer} buffer - The buffer containing the packet data.
328-
* @return {string[]} An array of parsed lines.
346+
* @return {[string[], number]} An array containing the parsed lines and the offset after the last parsed line/flush packet.
329347
*/
330-
const parsePacketLines = (buffer: Buffer): string[] => {
348+
const parsePacketLines = (buffer: Buffer): [string[], number] => {
331349
const lines: string[] = [];
332350
let offset = 0;
333351

334352
while (offset + 4 <= buffer.length) {
335353
const lengthHex = buffer.toString('utf8', offset, offset + 4);
336354
const length = parseInt(lengthHex, 16);
337355

356+
// Prevent non-hex characters from causing issues
357+
if (isNaN(length) || length < 0) {
358+
throw new Error(`Invalid packet line length ${lengthHex} at offset ${offset}`);
359+
}
360+
361+
// length of 0 indicates flush packet (0000)
338362
if (length === 0) {
339-
// Flush packet ("0000") marks the end of the ref section
363+
offset += 4; // Include length of the flush packet
340364
break;
341365
}
342366

367+
// Make sure we don't read past the end of the buffer
368+
if (offset + length > buffer.length) {
369+
throw new Error(`Invalid packet line length ${lengthHex} at offset ${offset}`);
370+
}
371+
343372
const line = buffer.toString('utf8', offset + 4, offset + length);
344373
lines.push(line);
345-
offset += length;
374+
offset += length; // Move offset to the start of the next line's length prefix
346375
}
347-
return lines;
376+
return [lines, offset];
348377
}
349378

350379
exec.displayName = 'parsePush.exec';
@@ -353,5 +382,6 @@ export {
353382
exec,
354383
getCommitData,
355384
getPackMeta,
385+
parsePacketLines,
356386
unpack
357387
};

0 commit comments

Comments
 (0)