[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#992114: bullseye-pu: package node-tar/6.0.5+ds1+~cs11.3.9-1+deb11u1



Package: release.debian.org
Severity: normal
Tags: bullseye
User: release.debian.org@packages.debian.org
Usertags: pu

[ Reason ]
node-tar is vulnerable to 2 CVE:
 * #992110, CVE-2021-32803: arbitrary File Creation/Overwrite
   vulnerability via insufficient symlink protection
 * #992111, CVE-2021-32804: arbitrary File Creation/Overwrite
   vulnerability due to insufficient absolute path sanitization

[ Impact ]
2 medium vulnerabilities

[ Tests ]
Test updated (not fully launched because it needs a newer node-tap)

[ Risks ]
Low risk: test passed and upstream patch applied with minor changes

[ Checklist ]
  [X] *all* changes are documented in the d/changelog
  [X] I reviewed all changes and I approve them
  [X] attach debdiff against the package in (old)stable
  [X] the issue is verified as fixed in unstable

[ Changes ]
Add new checks

Cheers,
Yadd
diff --git a/debian/changelog b/debian/changelog
index f8f5426..e16bf2f 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,13 @@
+node-tar (6.0.5+ds1+~cs11.3.9-1+deb11u1) bullseye; urgency=medium
+
+  * Team upload
+  * Remove paths from dirCache when no longer dirs
+    (Closes: #992110, CVE-2021-32803)
+  * Strip absolute paths more comprehensively
+    (Closes: #992111, CVE-2021-32804)
+
+ -- Yadd <yadd@debian.org>  Wed, 11 Aug 2021 21:50:15 +0200
+
 node-tar (6.0.5+ds1+~cs11.3.9-1) unstable; urgency=medium
 
   [ Xavier Guimard ]
diff --git a/debian/patches/CVE-2021-32803.patch b/debian/patches/CVE-2021-32803.patch
new file mode 100644
index 0000000..5328879
--- /dev/null
+++ b/debian/patches/CVE-2021-32803.patch
@@ -0,0 +1,106 @@
+Description: Remove paths from dirCache when no longer dirs
+Author: isaacs <i@izs.me>
+Origin: upstream, https://github.com/npm/node-tar/commit/9dbdeb6
+Bug: https://github.com/npm/node-tar/security/advisories/GHSA-r628-mhmh-qjhw
+Bug-Debian: https://bugs.debian.org/992110
+Forwarded: not-needed
+Reviewed-By: Yadd <yadd@debian.org>
+Last-Update: 2021-08-11
+
+--- a/lib/unpack.js
++++ b/lib/unpack.js
+@@ -461,6 +461,19 @@
+     this.reservations.reserve(paths, done => this[CHECKFS2](entry, done))
+   }
+   [CHECKFS2] (entry, done) {
++    // if we are not creating a directory, and the path is in the dirCache,
++    // then that means we are about to delete the directory we created
++    // previously, and it is no longer going to be a directory, and neither
++    // is any of its children.
++    if (entry.type !== 'Directory') {
++      for (const path of this.dirCache.keys()) {
++        if (path === entry.absolute ||
++            path.indexOf(entry.absolute + '/') === 0 ||
++            path.indexOf(entry.absolute + '\\') === 0)
++          this.dirCache.delete(path)
++      }
++    }
++
+     this[MKDIR](path.dirname(entry.absolute), this.dmode, er => {
+       if (er) {
+         done()
+@@ -528,6 +541,15 @@
+   }
+ 
+   [CHECKFS] (entry) {
++    if (entry.type !== 'Directory') {
++      for (const path of this.dirCache.keys()) {
++        if (path === entry.absolute ||
++            path.indexOf(entry.absolute + '/') === 0 ||
++            path.indexOf(entry.absolute + '\\') === 0)
++          this.dirCache.delete(path)
++      }
++    }
++
+     const er = this[MKDIR](path.dirname(entry.absolute), this.dmode, neverCalled)
+     if (er)
+       return this[ONERROR](er, entry)
+--- a/test/unpack.js
++++ b/test/unpack.js
+@@ -2577,3 +2577,56 @@
+     cwd: dir + '/sync', strict: true,
+   }).end(data), poop, 'sync')
+ })
++
++t.test('drop entry from dirCache if no longer a directory', t => {
++  const dir = path.resolve(unpackdir, 'dir-cache-error')
++  mkdirp.sync(dir + '/sync/y')
++  mkdirp.sync(dir + '/async/y')
++  const data = makeTar([
++    {
++      path: 'x',
++      type: 'Directory',
++    },
++    {
++      path: 'x',
++      type: 'SymbolicLink',
++      linkpath: './y',
++    },
++    {
++      path: 'x/ginkoid',
++      type: 'File',
++      size: 'ginkoid'.length,
++    },
++    'ginkoid',
++    '',
++    '',
++  ])
++  t.plan(2)
++  const WARNINGS = {}
++  const check = (t, path) => {
++    t.equal(fs.statSync(path + '/x').isDirectory(), true)
++    t.equal(fs.lstatSync(path + '/x').isSymbolicLink(), true)
++    t.equal(fs.statSync(path + '/y').isDirectory(), true)
++    t.strictSame(fs.readdirSync(path + '/y'), [])
++    t.throws(() => fs.readFileSync(path + '/x/ginkoid'), { code: 'ENOENT' })
++    t.strictSame(WARNINGS[path], [
++      'TAR_ENTRY_ERROR',
++      'Cannot extract through symbolic link',
++    ])
++    t.end()
++  }
++  t.test('async', t => {
++    const path = dir + '/async'
++    new Unpack({ cwd: path })
++      .on('warn', (code, msg) => WARNINGS[path] = [code, msg])
++      .on('end', () => check(t, path))
++      .end(data)
++  })
++  t.test('sync', t => {
++    const path = dir + '/sync'
++    new UnpackSync({ cwd: path })
++      .on('warn', (code, msg) => WARNINGS[path] = [code, msg])
++      .end(data)
++    check(t, path)
++  })
++})
diff --git a/debian/patches/CVE-2021-32804.patch b/debian/patches/CVE-2021-32804.patch
new file mode 100644
index 0000000..304892d
--- /dev/null
+++ b/debian/patches/CVE-2021-32804.patch
@@ -0,0 +1,175 @@
+Description: strip absolute paths more comprehensively
+Author: isaacs <i@izs.me>
+Origin: upstream, https://github.com/npm/node-tar/commit/1f036ca2
+Bug: https://github.com/npm/node-tar/security/advisories/GHSA-3jfq-g458-7qm9
+Bug-Debian: https://bugs.debian.org/992111
+Forwarded: not-needed
+Reviewed-By: Yadd <yadd@debian.org>
+Last-Update: 2021-08-11
+
+--- /dev/null
++++ b/lib/strip-absolute-path.js
+@@ -0,0 +1,14 @@
++// unix absolute paths are also absolute on win32, so we use this for both
++const { isAbsolute, parse } = require('path').win32
++
++// returns [root, stripped]
++module.exports = path => {
++  let r = ''
++  while (isAbsolute(path)) {
++    // windows will think that //x/y/z has a "root" of //x/y/
++    const root = path.charAt(0) === '/' ? '/' : parse(path).root
++    path = path.substr(root.length)
++    r += root
++  }
++  return [r, path]
++}
+--- a/lib/unpack.js
++++ b/lib/unpack.js
+@@ -16,6 +16,7 @@
+ const mkdirSync = mkdir.sync
+ const wc = require('./winchars.js')
+ const pathReservations = require('./path-reservations.js')
++const stripAbsolutePath = require('./strip-absolute-path.js')
+ 
+ const ONENTRY = Symbol('onEntry')
+ const CHECKFS = Symbol('checkFs')
+@@ -223,11 +224,10 @@
+ 
+       // absolutes on posix are also absolutes on win32
+       // so we only need to test this one to get both
+-      if (path.win32.isAbsolute(p)) {
+-        const parsed = path.win32.parse(p)
+-        entry.path = p.substr(parsed.root.length)
+-        const r = parsed.root
+-        this.warn('TAR_ENTRY_INFO', `stripping ${r} from absolute path`, {
++      const [root, stripped] = stripAbsolutePath(p)
++      if (root) {
++        entry.path = stripped
++        this.warn('TAR_ENTRY_INFO', `stripping ${root} from absolute path`, {
+           entry,
+           path: p,
+         })
+--- a/lib/write-entry.js
++++ b/lib/write-entry.js
+@@ -25,6 +25,7 @@
+ const MODE = Symbol('mode')
+ const warner = require('./warn-mixin.js')
+ const winchars = require('./winchars.js')
++const stripAbsolutePath = require('./strip-absolute-path.js')
+ 
+ const modeFix = require('./mode-fix.js')
+ 
+@@ -54,12 +55,12 @@
+       this.on('warn', opt.onwarn)
+ 
+     let pathWarn = false
+-    if (!this.preservePaths && path.win32.isAbsolute(p)) {
+-      // absolutes on posix are also absolutes on win32
+-      // so we only need to test this one to get both
+-      const parsed = path.win32.parse(p)
+-      this.path = p.substr(parsed.root.length)
+-      pathWarn = parsed.root
++    if (!this.preservePaths) {
++      const [root, stripped] = stripAbsolutePath(this.path)
++      if (root) {
++        this.path = stripped
++        pathWarn = root
++      }
+     }
+ 
+     this.win32 = !!opt.win32 || process.platform === 'win32'
+@@ -353,10 +354,12 @@
+       this.on('warn', opt.onwarn)
+ 
+     let pathWarn = false
+-    if (path.isAbsolute(this.path) && !this.preservePaths) {
+-      const parsed = path.parse(this.path)
+-      pathWarn = parsed.root
+-      this.path = this.path.substr(parsed.root.length)
++    if (!this.preservePaths) {
++      const [root, stripped] = stripAbsolutePath(this.path)
++      if (root) {
++        this.path = stripped
++        pathWarn = root
++      }
+     }
+ 
+     this.remain = readEntry.size
+--- /dev/null
++++ b/test/strip-absolute-path.js
+@@ -0,0 +1,14 @@
++const t = require('tap')
++const stripAbsolutePath = require('../lib/strip-absolute-path.js')
++
++const cases = {
++  '/': ['/', ''],
++  '////': ['////', ''],
++  'c:///a/b/c': ['c:///', 'a/b/c'],
++  '\\\\foo\\bar\\baz': ['\\\\foo\\bar\\', 'baz'],
++  '//foo//bar//baz': ['//', 'foo//bar//baz'],
++  'c:\\c:\\c:\\c:\\\\d:\\e/f/g': ['c:\\c:\\c:\\c:\\\\d:\\', 'e/f/g'],
++}
++
++for (const [input, [root, stripped]] of Object.entries(cases))
++  t.strictSame(stripAbsolutePath(input), [root, stripped], input)
+--- a/test/unpack.js
++++ b/test/unpack.js
+@@ -780,6 +780,9 @@
+   })
+ 
+   const absolute = path.resolve(dir, 'd/i/r/absolute')
++  const root = path.parse(absolute).root
++  const extraAbsolute = root + root + root + absolute
++  t.ok(path.isAbsolute(extraAbsolute))
+   t.ok(path.isAbsolute(absolute))
+   const parsed = path.parse(absolute)
+   const relative = absolute.substr(parsed.root.length)
+@@ -787,7 +790,7 @@
+ 
+   const data = makeTar([
+     {
+-      path: absolute,
++      path: extraAbsolute,
+       type: 'File',
+       size: 1,
+       atime: new Date('1979-07-01T19:10:00.000Z'),
+@@ -802,7 +805,7 @@
+   t.test('warn and correct', t => {
+     const check = t => {
+       t.match(warnings, [[
+-        'stripping / from absolute path',
++        `stripping ${root}${root}${root}${root} from absolute path`,
+         { path: absolute, code: 'TAR_ENTRY_INFO' },
+       ]])
+       t.ok(fs.lstatSync(path.resolve(dir, relative)).isFile(), 'is file')
+--- a/test/write-entry.js
++++ b/test/write-entry.js
+@@ -399,7 +399,10 @@
+ 
+ /*
+ t.test('absolute path', t => {
+-  const f = path.resolve(files, '512-bytes.txt')
++  const absolute = path.resolve(files, '512-bytes.txt')
++  const { root } = path.parse(absolute)
++  const f = root + root + root + absolute
++  const warn = root + root + root + root
+   t.test('preservePaths=false strict=false', t => {
+     const warnings = []
+     const ws = new WriteEntry(f, {
+@@ -412,13 +415,13 @@
+       out = Buffer.concat(out)
+       t.equal(out.length, 1024)
+       t.match(warnings, [[
+-        'TAR_ENTRY_INFO', /stripping .* from absolute path/, { path: f }
++        'TAR_ENTRY_INFO', `stripping ${warn} from absolute path`, { path: f },
+       ]])
+ 
+       t.match(ws.header, {
+         cksumValid: true,
+         needPax: false,
+-        path: f.replace(/^(\/|[a-z]:\\\\)/, ''),
++        path: f.replace(/^(\/|[a-z]:\\\\){4}/, ''),
+         mode: 0o644,
+         size: 512,
+         linkpath: null,
diff --git a/debian/patches/series b/debian/patches/series
index 30956fd..931f724 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1,2 +1,4 @@
 api-backward-compatibility.patch
 disable-failing-tests.diff
+CVE-2021-32803.patch
+CVE-2021-32804.patch

Reply to: