Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: isaacs/node-tar
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v6.2.0
Choose a base ref
...
head repository: isaacs/node-tar
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v6.2.1
Choose a head ref
  • 3 commits
  • 7 files changed
  • 1 contributor

Commits on Feb 12, 2024

  1. remove security.md

    isaacs committed Feb 12, 2024
    Copy the full SHA
    fe7ebfd View commit details

Commits on Mar 21, 2024

  1. prevent extraction in excessively deep subfolders

    This sets the limit at 1024 subfolders nesting by default, but that can
    be dropped down, or set to Infinity to remove the limitation.
    isaacs committed Mar 21, 2024
    Copy the full SHA
    fe8cd57 View commit details
  2. 6.2.1

    isaacs committed Mar 21, 2024
    Copy the full SHA
    bef7b1e View commit details
Showing with 95 additions and 21 deletions.
  1. +10 −0 README.md
  2. +0 −14 SECURITY.md
  3. +22 −5 lib/unpack.js
  4. +1 −1 package.json
  5. BIN test/fixtures/excessively-deep.tar
  6. +1 −1 test/parse.js
  7. +61 −0 test/unpack.js
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -115,6 +115,8 @@ Handlers receive 3 arguments:
encountered an error which prevented it from being unpacked. This occurs
when:
- an unrecoverable fs error happens during unpacking,
- an entry is trying to extract into an excessively deep
location (by default, limited to 1024 subfolders),
- an entry has `..` in the path and `preservePaths` is not set, or
- an entry is extracting through a symbolic link, when `preservePaths` is
not set.
@@ -427,6 +429,10 @@ The following options are supported:
`process.umask()` to determine the default umask value, since tar will
extract with whatever mode is provided, and let the process `umask` apply
normally.
- `maxDepth` The maximum depth of subfolders to extract into. This
defaults to 1024. Anything deeper than the limit will raise a
warning and skip the entry. Set to `Infinity` to remove the
limitation.

The following options are mostly internal, but can be modified in some
advanced use cases, such as re-using caches between runs.
@@ -749,6 +755,10 @@ Most unpack errors will cause a `warn` event to be emitted. If the
`process.umask()` to determine the default umask value, since tar will
extract with whatever mode is provided, and let the process `umask` apply
normally.
- `maxDepth` The maximum depth of subfolders to extract into. This
defaults to 1024. Anything deeper than the limit will raise a
warning and skip the entry. Set to `Infinity` to remove the
limitation.

### class tar.Unpack.Sync

14 changes: 0 additions & 14 deletions SECURITY.md

This file was deleted.

27 changes: 22 additions & 5 deletions lib/unpack.js
Original file line number Diff line number Diff line change
@@ -48,6 +48,7 @@ const crypto = require('crypto')
const getFlag = require('./get-write-flag.js')
const platform = process.env.TESTING_TAR_FAKE_PLATFORM || process.platform
const isWindows = platform === 'win32'
const DEFAULT_MAX_DEPTH = 1024

// Unlinks on Windows are not atomic.
//
@@ -181,6 +182,12 @@ class Unpack extends Parser {
this.processGid = (this.preserveOwner || this.setOwner) && process.getgid ?
process.getgid() : null

// prevent excessively deep nesting of subfolders
// set to `Infinity` to remove this restriction
this.maxDepth = typeof opt.maxDepth === 'number'
? opt.maxDepth
: DEFAULT_MAX_DEPTH

// mostly just for testing, but useful in some cases.
// Forcibly trigger a chown on every entry, no matter what
this.forceChown = opt.forceChown === true
@@ -238,13 +245,13 @@ class Unpack extends Parser {
}

[CHECKPATH] (entry) {
const p = normPath(entry.path)
const parts = p.split('/')

if (this.strip) {
const parts = normPath(entry.path).split('/')
if (parts.length < this.strip) {
return false
}
entry.path = parts.slice(this.strip).join('/')

if (entry.type === 'Link') {
const linkparts = normPath(entry.linkpath).split('/')
if (linkparts.length >= this.strip) {
@@ -253,11 +260,21 @@ class Unpack extends Parser {
return false
}
}
parts.splice(0, this.strip)
entry.path = parts.join('/')
}

if (isFinite(this.maxDepth) && parts.length > this.maxDepth) {
this.warn('TAR_ENTRY_ERROR', 'path excessively deep', {
entry,
path: p,
depth: parts.length,
maxDepth: this.maxDepth,
})
return false
}

if (!this.preservePaths) {
const p = normPath(entry.path)
const parts = p.split('/')
if (parts.includes('..') || isWindows && /^[a-z]:\.\.$/i.test(parts[0])) {
this.warn('TAR_ENTRY_ERROR', `path contains '..'`, {
entry,
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
"author": "GitHub Inc.",
"name": "tar",
"description": "tar for node",
"version": "6.2.0",
"version": "6.2.1",
"repository": {
"type": "git",
"url": "https://github.com/isaacs/node-tar.git"
Binary file added test/fixtures/excessively-deep.tar
Binary file not shown.
2 changes: 1 addition & 1 deletion test/parse.js
Original file line number Diff line number Diff line change
@@ -646,7 +646,7 @@ t.test('truncated gzip input', t => {
p.write(tgz.slice(split))
p.end()
t.equal(aborted, true, 'aborted writing')
t.same(warnings, ['zlib: incorrect data check'])
t.match(warnings, [/^zlib: /])
t.end()
})

61 changes: 61 additions & 0 deletions test/unpack.js
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@ const mkdirp = require('mkdirp')
const mutateFS = require('mutate-fs')
const eos = require('end-of-stream')
const normPath = require('../lib/normalize-windows-path.js')
const ReadEntry = require('../lib/read-entry.js')

// On Windows in particular, the "really deep folder path" file
// often tends to cause problems, which don't indicate a failure
@@ -3235,3 +3236,63 @@ t.test('recognize C:.. as a dot path part', t => {

t.end()
})

t.test('excessively deep subfolder nesting', async t => {
const tf = path.resolve(fixtures, 'excessively-deep.tar')
const data = fs.readFileSync(tf)
const warnings = []
const onwarn = (c, w, { entry, path, depth, maxDepth }) =>
warnings.push([c, w, { entry, path, depth, maxDepth }])

const check = (t, maxDepth = 1024) => {
t.match(warnings, [
['TAR_ENTRY_ERROR',
'path excessively deep',
{
entry: ReadEntry,
path: /^\.(\/a){1024,}\/foo.txt$/,
depth: 222372,
maxDepth,
}
]
])
warnings.length = 0
t.end()
}

t.test('async', t => {
const cwd = t.testdir()
new Unpack({
cwd,
onwarn
}).on('end', () => check(t)).end(data)
})

t.test('sync', t => {
const cwd = t.testdir()
new UnpackSync({
cwd,
onwarn
}).end(data)
check(t)
})

t.test('async set md', t => {
const cwd = t.testdir()
new Unpack({
cwd,
onwarn,
maxDepth: 64,
}).on('end', () => check(t, 64)).end(data)
})

t.test('sync set md', t => {
const cwd = t.testdir()
new UnpackSync({
cwd,
onwarn,
maxDepth: 64,
}).end(data)
check(t, 64)
})
})