diff --git a/README.md b/README.md index 7cb09da..f620568 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/debian/clean b/debian/clean index 40b758b..3b64941 100644 --- a/debian/clean +++ b/debian/clean @@ -1,2 +1,3 @@ +c*/ debian/node-tar.copyright test/fixtures/unpack/ diff --git a/debian/control b/debian/control index cbb0b9a..0882200 100644 --- a/debian/control +++ b/debian/control @@ -3,18 +3,21 @@ Maintainer: OpenKylin Developers Section: javascript Testsuite: autopkgtest-pkg-nodejs Priority: optional -Build-Depends: debhelper-compat (= 13), - dh-sequence-nodejs, - node-chownr , - node-end-of-stream , - node-minipass , - node-mkdirp (>= 1), - node-mutate-fs , - node-rimraf , - node-tap , - node-tar-stream , - node-yallist (>= 4.0~) -Standards-Version: 4.6.0 +Build-Depends: + debhelper-compat (= 13) + , dh-sequence-nodejs + , dh-nodejs + , node-chownr + , node-end-of-stream + , node-mkdirp (>= 1) + , node-minipass + , node-mutate-fs + , node-nock + , node-rimraf + , node-tap (>= 15) + , node-tar-stream + , node-yallist (>= 4.0~) +Standards-Version: 4.6.2 Vcs-Browser: https://gitee.com/openkylin/node-tar Vcs-Git: https://gitee.com/openkylin/node-tar.git Homepage: https://github.com/npm/node-tar @@ -22,13 +25,16 @@ Rules-Requires-Root: no Package: node-tar Architecture: all -Depends: node-chownr, - node-minipass, - node-mkdirp (>= 1), - node-safe-buffer, - node-yallist (>= 4.0~), - ${misc:Depends} -Provides: ${nodejs:Provides} +Depends: + ${misc:Depends} + , node-chownr + , node-mkdirp (>= 1) + , node-minipass + , node-safe-buffer + , node-yallist (>= 4.0~) +Provides: + ${nodejs:Provides} +Multi-Arch: foreign Description: read and write portable tar archives module for Node.js node-tar is able to read and write tar archives generated by bsdtar, gnutar, solaris posix tar, and "Schilly" tar. diff --git a/debian/gbp.conf b/debian/gbp.conf index 8f45e69..05ffe9a 100644 --- a/debian/gbp.conf +++ b/debian/gbp.conf @@ -1,2 +1,16 @@ +[DEFAULT] +# The default name for the upstream branch is "upstream". +# Change it if the name is different (for instance, "master"). +upstream-branch=upstream +# The default name for the Debian branch is "master". +# Change it if the name is different (for instance, "debian/unstable"). +debian-branch=master +# git-import-orig uses the following names for the upstream tags. +# Change the value if you are not using git-import-orig +upstream-tag=upstream/%(version)s +# Always use pristine-tar. +pristine-tar=True +component=['fs-minipass', 'types-tar'] + [import-orig] filter=[ '.gitattributes', '.gitignore', '.travis.yml', '.git*' ] diff --git a/debian/rules b/debian/rules index 050d8c6..b1ce6a2 100755 --- a/debian/rules +++ b/debian/rules @@ -10,3 +10,7 @@ override_dh_fixperms: dh_fixperms chmod -x debian/node-tar/usr/share/nodejs/@types/tar/* + +override_dh_installdocs: + dh_installdocs + dh_nodejs_autodocs diff --git a/debian/tests/pkg-js/files b/debian/tests/pkg-js/files index 4057de2..abd6f29 100644 --- a/debian/tests/pkg-js/files +++ b/debian/tests/pkg-js/files @@ -1,4 +1,5 @@ debian/tests/excluded +debian/tests/fixtures debian/tests/test_modules map.js README.md diff --git a/debian/tests/pkg-js/test b/debian/tests/pkg-js/test index 4898208..abf0470 100644 --- a/debian/tests/pkg-js/test +++ b/debian/tests/pkg-js/test @@ -1,3 +1,4 @@ export TAP_TIMEOUT=60 -export NODE_PATH=debian/tests/test_modules:node_modules -tap `ls test/*.js|grep -v -f debian/tests/excluded` +cp debian/tests/fixtures/excessively-deep.tar test/fixtures/ || true +tap --no-cov -R dot test/*.js +rm -f test/fixtures/excessively-deep.tar diff --git a/debian/watch b/debian/watch index b3ae3da..77ebfe8 100644 --- a/debian/watch +++ b/debian/watch @@ -10,7 +10,7 @@ ctype=nodejs,\ component=fs-minipass,\ dversionmangle=auto,\ filenamemangle=s/.*?(\d[\d\.-]*@ARCHIVE_EXT@)/node-fs-minipass-$1/ \ - https://github.com/npm/fs-minipass/releases .*/archive/.*/v?([\d\.]+).tar.gz checksum + https://github.com/npm/fs-minipass/tags .*/archive/.*/v?([\d\.]+).tar.gz checksum # It is not recommended use npmregistry. Please investigate more. # Take a look at https://wiki.debian.org/debian/watch/ diff --git a/index.js b/index.js index c9ae06e..1c0b826 100644 --- a/index.js +++ b/index.js @@ -1,11 +1,11 @@ 'use strict' // high-level commands -exports.c = exports.create = require('./lib/create.js') -exports.r = exports.replace = require('./lib/replace.js') -exports.t = exports.list = require('./lib/list.js') -exports.u = exports.update = require('./lib/update.js') -exports.x = exports.extract = require('./lib/extract.js') +exports.c = exports.create = exports.Create = require('./lib/create.js') +exports.r = exports.replace = exports.Replace = require('./lib/replace.js') +exports.t = exports.list = exports.List = require('./lib/list.js') +exports.u = exports.update = exports.Update = require('./lib/update.js') +exports.x = exports.extract = exports.Extract = require('./lib/extract.js') // classes exports.Pack = require('./lib/pack.js') diff --git a/lib/unpack.js b/lib/unpack.js index e341ad0..6b4ba4e 100644 --- a/lib/unpack.js +++ b/lib/unpack.js @@ -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, diff --git a/test/parse.js b/test/parse.js index 97705e4..3961b45 100644 --- a/test/parse.js +++ b/test/parse.js @@ -551,7 +551,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() }) diff --git a/test/unpack.js b/test/unpack.js index b23aab3..198f034 100644 --- a/test/unpack.js +++ b/test/unpack.js @@ -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 @@ -3233,3 +3234,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) + }) +})