@@ -28,7 +28,8 @@ async function exec(req: any, action: Action): Promise<Action> {
28
28
action . addStep ( step ) ;
29
29
return action ;
30
30
}
31
- const packetLines = parsePacketLines ( req . body ) ;
31
+ const [ packetLines , packDataOffset ] = parsePacketLines ( req . body ) ;
32
+
32
33
const refUpdates = packetLines . filter ( ( line ) => line . includes ( 'refs/heads/' ) ) ;
33
34
34
35
if ( refUpdates . length !== 1 ) {
@@ -45,8 +46,24 @@ async function exec(req: any, action: Action): Promise<Action> {
45
46
action . branch = ref . replace ( / \0 .* / , '' ) . trim ( ) ;
46
47
action . setCommit ( oldCommit , newCommit ) ;
47
48
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
+
50
67
const [ meta , contentBuff ] = getPackMeta ( buf ) ;
51
68
const contents = getContents ( contentBuff as any , meta . entries as number ) ;
52
69
@@ -324,27 +341,39 @@ const unpack = (buf: Buffer) => {
324
341
325
342
/**
326
343
* 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).
327
345
* @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 .
329
347
*/
330
- const parsePacketLines = ( buffer : Buffer ) : string [ ] => {
348
+ const parsePacketLines = ( buffer : Buffer ) : [ string [ ] , number ] => {
331
349
const lines : string [ ] = [ ] ;
332
350
let offset = 0 ;
333
351
334
352
while ( offset + 4 <= buffer . length ) {
335
353
const lengthHex = buffer . toString ( 'utf8' , offset , offset + 4 ) ;
336
354
const length = parseInt ( lengthHex , 16 ) ;
337
355
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)
338
362
if ( length === 0 ) {
339
- // Flush packet ("0000") marks the end of the ref section
363
+ offset += 4 ; // Include length of the flush packet
340
364
break ;
341
365
}
342
366
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
+
343
372
const line = buffer . toString ( 'utf8' , offset + 4 , offset + length ) ;
344
373
lines . push ( line ) ;
345
- offset += length ;
374
+ offset += length ; // Move offset to the start of the next line's length prefix
346
375
}
347
- return lines ;
376
+ return [ lines , offset ] ;
348
377
}
349
378
350
379
exec . displayName = 'parsePush.exec' ;
@@ -353,5 +382,6 @@ export {
353
382
exec ,
354
383
getCommitData ,
355
384
getPackMeta ,
385
+ parsePacketLines ,
356
386
unpack
357
387
} ;
0 commit comments