@@ -42,22 +42,45 @@ export class Header {
4242 if ( ! buf || ! ( buf . length >= off + 512 ) ) {
4343 throw new Error ( 'need 512 bytes for header' ) ;
4444 }
45- this . path = ex ?. path ?? decString ( buf , off , 100 ) ;
46- this . mode = ex ?. mode ?? gex ?. mode ?? decNumber ( buf , off + 100 , 8 ) ;
47- this . uid = ex ?. uid ?? gex ?. uid ?? decNumber ( buf , off + 108 , 8 ) ;
48- this . gid = ex ?. gid ?? gex ?. gid ?? decNumber ( buf , off + 116 , 8 ) ;
49- this . size = ex ?. size ?? gex ?. size ?? decNumber ( buf , off + 124 , 12 ) ;
50- this . mtime = ex ?. mtime ?? gex ?. mtime ?? decDate ( buf , off + 136 , 12 ) ;
45+ // Decode the typeflag (independent of any pending PAX/GNU extended header)
46+ // up front so we can tell whether THIS block is itself an intermediary
47+ // extension header (PAX `x`/`g`, GNU long-name `L`, GNU long-link `K`).
48+ // Per POSIX pax, a PAX extended header describes the *next file entry*, not
49+ // the extension headers that may sit between it and that file. Applying the
50+ // pending PAX overrides (notably `size`) to an intervening `L`/`K`/`x`/`g`
51+ // header desynchronizes the stream relative to other tar implementations
52+ // and enables tar interpretation-conflict / file-smuggling attacks.
53+ const t = decString ( buf , off + 156 , 1 ) ;
54+ const isNormalFS = types . normalFsTypes . has ( t ) ;
55+ const exForFields = isNormalFS ? ex : undefined ;
56+ const gexForFields = isNormalFS ? gex : undefined ;
57+ this . path = exForFields ?. path ?? decString ( buf , off , 100 ) ;
58+ this . mode =
59+ exForFields ?. mode ??
60+ gexForFields ?. mode ??
61+ decNumber ( buf , off + 100 , 8 ) ;
62+ this . uid =
63+ exForFields ?. uid ?? gexForFields ?. uid ?? decNumber ( buf , off + 108 , 8 ) ;
64+ this . gid =
65+ exForFields ?. gid ?? gexForFields ?. gid ?? decNumber ( buf , off + 116 , 8 ) ;
66+ this . size =
67+ exForFields ?. size ??
68+ gexForFields ?. size ??
69+ decNumber ( buf , off + 124 , 12 ) ;
70+ this . mtime =
71+ exForFields ?. mtime ??
72+ gexForFields ?. mtime ??
73+ decDate ( buf , off + 136 , 12 ) ;
5174 this . cksum = decNumber ( buf , off + 148 , 12 ) ;
5275 // if we have extended or global extended headers, apply them now
5376 // See https://github.com/npm/node-tar/pull/187
54- // Apply global before local, so it overrides
55- if ( gex )
56- this . #slurp( gex , true ) ;
57- if ( ex )
58- this . #slurp( ex ) ;
77+ // Apply global before local, so it overrides. Never slurp the pending
78+ // extended-header fields onto an intermediary extension header.
79+ if ( gexForFields )
80+ this . #slurp( gexForFields , true ) ;
81+ if ( exForFields )
82+ this . #slurp( exForFields ) ;
5983 // old tar versions marked dirs as a file with a trailing /
60- const t = decString ( buf , off + 156 , 1 ) ;
6184 if ( types . isCode ( t ) ) {
6285 this . #type = t || '0' ;
6386 }
@@ -75,12 +98,24 @@ export class Header {
7598 this . linkpath = decString ( buf , off + 157 , 100 ) ;
7699 if ( buf . subarray ( off + 257 , off + 265 ) . toString ( ) === 'ustar\u000000' ) {
77100 /* c8 ignore start */
78- this . uname = ex ?. uname ?? gex ?. uname ?? decString ( buf , off + 265 , 32 ) ;
79- this . gname = ex ?. gname ?? gex ?. gname ?? decString ( buf , off + 297 , 32 ) ;
101+ this . uname =
102+ exForFields ?. uname ??
103+ gexForFields ?. uname ??
104+ decString ( buf , off + 265 , 32 ) ;
105+ this . gname =
106+ exForFields ?. gname ??
107+ gexForFields ?. gname ??
108+ decString ( buf , off + 297 , 32 ) ;
80109 this . devmaj =
81- ex ?. devmaj ?? gex ?. devmaj ?? decNumber ( buf , off + 329 , 8 ) ?? 0 ;
110+ exForFields ?. devmaj ??
111+ gexForFields ?. devmaj ??
112+ decNumber ( buf , off + 329 , 8 ) ??
113+ 0 ;
82114 this . devmin =
83- ex ?. devmin ?? gex ?. devmin ?? decNumber ( buf , off + 337 , 8 ) ?? 0 ;
115+ exForFields ?. devmin ??
116+ gexForFields ?. devmin ??
117+ decNumber ( buf , off + 337 , 8 ) ??
118+ 0 ;
84119 /* c8 ignore stop */
85120 if ( buf [ off + 475 ] !== 0 ) {
86121 // definitely a prefix, definitely >130 chars.
0 commit comments