From fe7ebfdcede1f8a2e65db12e19ecc4b3a9934648 Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 12 Feb 2024 10:58:12 -0800 Subject: [PATCH 1/3] remove security.md Fix: https://github.com/isaacs/node-tar/security/advisories/GHSA-hr8v-f3j8-8w2m --- SECURITY.md | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 4e7c26c6..00000000 --- a/SECURITY.md +++ /dev/null @@ -1,14 +0,0 @@ - - -GitHub takes the security of our software products and services seriously, including the open source code repositories managed through our GitHub organizations, such as [GitHub](https://github.com/GitHub). - -If you believe you have found a security vulnerability in this GitHub-owned open source repository, you can report it to us in one of two ways. - -If the vulnerability you have found is *not* [in scope for the GitHub Bug Bounty Program](https://bounty.github.com/#scope) or if you do not wish to be considered for a bounty reward, please report the issue to us directly using [private vulnerability reporting](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability). - -If the vulnerability you have found is [in scope for the GitHub Bug Bounty Program](https://bounty.github.com/#scope) and you would like for your finding to be considered for a bounty reward, please submit the vulnerability to us through [HackerOne](https://hackerone.com/github) in order to be eligible to receive a bounty award. - -**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** - -Thanks for helping make GitHub safe for everyone. - From fe8cd57da5686f8695415414bda49206a545f7f7 Mon Sep 17 00:00:00 2001 From: isaacs Date: Fri, 15 Mar 2024 22:50:56 -0700 Subject: [PATCH 2/3] 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. --- README.md | 10 +++++ lib/unpack.js | 27 ++++++++++--- test/fixtures/excessively-deep.tar | Bin 0 -> 450560 bytes test/parse.js | 2 +- test/unpack.js | 61 +++++++++++++++++++++++++++++ 5 files changed, 94 insertions(+), 6 deletions(-) create mode 100644 test/fixtures/excessively-deep.tar diff --git a/README.md b/README.md index 7cb09da6..f620568e 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/lib/unpack.js b/lib/unpack.js index fa46611c..03172e2c 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/fixtures/excessively-deep.tar b/test/fixtures/excessively-deep.tar new file mode 100644 index 0000000000000000000000000000000000000000..e42aa3dab63338cf5036d99426efae716c42b892 GIT binary patch literal 450560 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 V0RjXF5FkK+009C72oU%!flp(o{=)zO literal 0 HcmV?d00001 diff --git a/test/parse.js b/test/parse.js index 549b3701..2cc68782 100644 --- a/test/parse.js +++ b/test/parse.js @@ -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() }) diff --git a/test/unpack.js b/test/unpack.js index 399ae0e6..2f1d3026 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 @@ -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) + }) +}) From bef7b1e4ffab822681fea2a9b22187192ed14717 Mon Sep 17 00:00:00 2001 From: isaacs Date: Thu, 21 Mar 2024 14:12:18 -0700 Subject: [PATCH 3/3] 6.2.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 46d91ee1..f84a41cc 100644 --- a/package.json +++ b/package.json @@ -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"