@@ -41,6 +41,7 @@ exports.Header = void 0;
4141const node_path_1 = require ( "node:path" ) ;
4242const large = __importStar ( require ( "./large-numbers.js" ) ) ;
4343const types = __importStar ( require ( "./types.js" ) ) ;
44+ const notNegative = ( n ) => n === undefined || n < 0 ? undefined : n ;
4445class Header {
4546 cksumValid = false ;
4647 needPax = false ;
@@ -78,23 +79,44 @@ class Header {
7879 if ( ! buf || ! ( buf . length >= off + 512 ) ) {
7980 throw new Error ( 'need 512 bytes for header' ) ;
8081 }
81- this . path = ex ?. path ?? decString ( buf , off , 100 ) ;
82- this . mode = ex ?. mode ?? gex ?. mode ?? decNumber ( buf , off + 100 , 8 ) ;
83- this . uid = ex ?. uid ?? gex ?. uid ?? decNumber ( buf , off + 108 , 8 ) ;
84- this . gid = ex ?. gid ?? gex ?. gid ?? decNumber ( buf , off + 116 , 8 ) ;
85- this . size = ex ?. size ?? gex ?. size ?? decNumber ( buf , off + 124 , 12 ) ;
82+ // Decode the typeflag (independent of any pending PAX/GNU extended header)
83+ // up front so we can tell whether THIS block is itself an intermediary
84+ // extension header (PAX `x`/`g`, GNU long-name `L`, GNU long-link `K`).
85+ // Per POSIX pax, a PAX extended header describes the *next file entry*, not
86+ // the extension headers that may sit between it and that file. Applying the
87+ // pending PAX overrides (notably `size`) to an intervening `L`/`K`/`x`/`g`
88+ // header desynchronizes the stream relative to other tar implementations
89+ // and enables tar interpretation-conflict / file-smuggling attacks.
90+ const t = decString ( buf , off + 156 , 1 ) ;
91+ const isNormalFS = types . normalFsTypes . has ( t ) ;
92+ const exForFields = isNormalFS ? ex : undefined ;
93+ const gexForFields = isNormalFS ? gex : undefined ;
94+ this . path = exForFields ?. path ?? decString ( buf , off , 100 ) ;
95+ this . mode =
96+ exForFields ?. mode ??
97+ gexForFields ?. mode ??
98+ decNumber ( buf , off + 100 , 8 ) ;
99+ this . uid =
100+ exForFields ?. uid ?? gexForFields ?. uid ?? decNumber ( buf , off + 108 , 8 ) ;
101+ this . gid =
102+ exForFields ?. gid ?? gexForFields ?. gid ?? decNumber ( buf , off + 116 , 8 ) ;
103+ this . size = notNegative ( exForFields ?. size ??
104+ gexForFields ?. size ??
105+ decNumber ( buf , off + 124 , 12 ) ) ;
86106 this . mtime =
87- ex ?. mtime ?? gex ?. mtime ?? decDate ( buf , off + 136 , 12 ) ;
107+ exForFields ?. mtime ??
108+ gexForFields ?. mtime ??
109+ decDate ( buf , off + 136 , 12 ) ;
88110 this . cksum = decNumber ( buf , off + 148 , 12 ) ;
89111 // if we have extended or global extended headers, apply them now
90112 // See https://github.com/npm/node-tar/pull/187
91- // Apply global before local, so it overrides
92- if ( gex )
93- this . #slurp( gex , true ) ;
94- if ( ex )
95- this . #slurp( ex ) ;
113+ // Apply global before local, so it overrides. Never slurp the pending
114+ // extended-header fields onto an intermediary extension header.
115+ if ( gexForFields )
116+ this . #slurp( gexForFields , true ) ;
117+ if ( exForFields )
118+ this . #slurp( exForFields ) ;
96119 // old tar versions marked dirs as a file with a trailing /
97- const t = decString ( buf , off + 156 , 1 ) ;
98120 if ( types . isCode ( t ) ) {
99121 this . #type = t || '0' ;
100122 }
@@ -110,17 +132,26 @@ class Header {
110132 this . size = 0 ;
111133 }
112134 this . linkpath = decString ( buf , off + 157 , 100 ) ;
113- if ( buf . subarray ( off + 257 , off + 265 ) . toString ( ) ===
114- 'ustar\u000000' ) {
135+ if ( buf . subarray ( off + 257 , off + 265 ) . toString ( ) === 'ustar\u000000' ) {
115136 /* c8 ignore start */
116137 this . uname =
117- ex ?. uname ?? gex ?. uname ?? decString ( buf , off + 265 , 32 ) ;
138+ exForFields ?. uname ??
139+ gexForFields ?. uname ??
140+ decString ( buf , off + 265 , 32 ) ;
118141 this . gname =
119- ex ?. gname ?? gex ?. gname ?? decString ( buf , off + 297 , 32 ) ;
142+ exForFields ?. gname ??
143+ gexForFields ?. gname ??
144+ decString ( buf , off + 297 , 32 ) ;
120145 this . devmaj =
121- ex ?. devmaj ?? gex ?. devmaj ?? decNumber ( buf , off + 329 , 8 ) ?? 0 ;
146+ exForFields ?. devmaj ??
147+ gexForFields ?. devmaj ??
148+ decNumber ( buf , off + 329 , 8 ) ??
149+ 0 ;
122150 this . devmin =
123- ex ?. devmin ?? gex ?. devmin ?? decNumber ( buf , off + 337 , 8 ) ?? 0 ;
151+ exForFields ?. devmin ??
152+ gexForFields ?. devmin ??
153+ decNumber ( buf , off + 337 , 8 ) ??
154+ 0 ;
124155 /* c8 ignore stop */
125156 if ( buf [ off + 475 ] !== 0 ) {
126157 // definitely a prefix, definitely >130 chars.
@@ -133,10 +164,8 @@ class Header {
133164 this . path = prefix + '/' + this . path ;
134165 }
135166 /* c8 ignore start */
136- this . atime =
137- ex ?. atime ?? gex ?. atime ?? decDate ( buf , off + 476 , 12 ) ;
138- this . ctime =
139- ex ?. ctime ?? gex ?. ctime ?? decDate ( buf , off + 488 , 12 ) ;
167+ this . atime = ex ?. atime ?? gex ?. atime ?? decDate ( buf , off + 476 , 12 ) ;
168+ this . ctime = ex ?. ctime ?? gex ?. ctime ?? decDate ( buf , off + 488 , 12 ) ;
140169 /* c8 ignore stop */
141170 }
142171 }
@@ -159,6 +188,7 @@ class Header {
159188 // null/undefined values are ignored.
160189 return ! ( v === null ||
161190 v === undefined ||
191+ ( k === 'size' && Number ( v ) < 0 ) ||
162192 ( k === 'path' && gex ) ||
163193 ( k === 'linkpath' && gex ) ||
164194 k === 'global' ) ;
@@ -180,17 +210,12 @@ class Header {
180210 const prefix = split [ 1 ] ;
181211 this . needPax = ! ! split [ 2 ] ;
182212 this . needPax = encString ( buf , off , 100 , path ) || this . needPax ;
183- this . needPax =
184- encNumber ( buf , off + 100 , 8 , this . mode ) || this . needPax ;
185- this . needPax =
186- encNumber ( buf , off + 108 , 8 , this . uid ) || this . needPax ;
187- this . needPax =
188- encNumber ( buf , off + 116 , 8 , this . gid ) || this . needPax ;
189- this . needPax =
190- encNumber ( buf , off + 124 , 12 , this . size ) || this . needPax ;
191- this . needPax =
192- encDate ( buf , off + 136 , 12 , this . mtime ) || this . needPax ;
193- buf [ off + 156 ] = this . #type. charCodeAt ( 0 ) ;
213+ this . needPax = encNumber ( buf , off + 100 , 8 , this . mode ) || this . needPax ;
214+ this . needPax = encNumber ( buf , off + 108 , 8 , this . uid ) || this . needPax ;
215+ this . needPax = encNumber ( buf , off + 116 , 8 , this . gid ) || this . needPax ;
216+ this . needPax = encNumber ( buf , off + 124 , 12 , this . size ) || this . needPax ;
217+ this . needPax = encDate ( buf , off + 136 , 12 , this . mtime ) || this . needPax ;
218+ buf [ off + 156 ] = Number ( this . #type. codePointAt ( 0 ) ) ;
194219 this . needPax =
195220 encString ( buf , off + 157 , 100 , this . linkpath ) || this . needPax ;
196221 buf . write ( 'ustar\u000000' , off + 257 , 8 ) ;
@@ -205,12 +230,10 @@ class Header {
205230 this . needPax =
206231 encString ( buf , off + 345 , prefixSize , prefix ) || this . needPax ;
207232 if ( buf [ off + 475 ] !== 0 ) {
208- this . needPax =
209- encString ( buf , off + 345 , 155 , prefix ) || this . needPax ;
233+ this . needPax = encString ( buf , off + 345 , 155 , prefix ) || this . needPax ;
210234 }
211235 else {
212- this . needPax =
213- encString ( buf , off + 345 , 130 , prefix ) || this . needPax ;
236+ this . needPax = encString ( buf , off + 345 , 130 , prefix ) || this . needPax ;
214237 this . needPax =
215238 encDate ( buf , off + 476 , 12 , this . atime ) || this . needPax ;
216239 this . needPax =
0 commit comments