commit d955500acdf5ff707f7e1fa915929b75f06fc584 Author: Iskren Chernev Date: Tue Jul 5 14:09:24 2022 +0300 Improve data pipeline Thanks to Michael V. Scovetta from Alpha-Omega (part of Open Source Security Foundation) for reporting. TL;DR; build scripts were using node's exec with a single string for command and arguments, which could lead to arbitrary command execution from the build script (if you pass a specific parameter to a grunt task). New code uses execFile, which has separate command and args, and also uses IANA's HTTPS endpoints, so no MITM is possible while downloading the latest tzdata. Security advisory link: https://github.com/openssf-omega/vulnerability-reports/security/advisories/GHSA-qgxp-x7vh-545j diff --git a/tasks/data-download.js b/tasks/data-download.js index f606cb2..1307878 100644 --- a/tasks/data-download.js +++ b/tasks/data-download.js @@ -1,32 +1,33 @@ "use strict"; var path = require('path'), - exec = require('child_process').exec; + execFile = require('child_process').execFile; module.exports = function (grunt) { grunt.registerTask('data-download', '1. Download data from iana.org/time-zones.', function (version) { version = version || 'latest'; var done = this.async(), - src = 'ftp://ftp.iana.org/tz/tzdata-latest.tar.gz', + src = (version === 'latest' ? + 'https://data.iana.org/time-zones/tzdata-latest.tar.gz' : + 'https://data.iana.org/time-zones/releases/tzdata' + version + '.tar.gz'), curl = path.resolve('temp/curl', version, 'data.tar.gz'), dest = path.resolve('temp/download', version); - if (version !== 'latest') { - src = 'https://data.iana.org/time-zones/releases/tzdata' + version + '.tar.gz'; - } - grunt.file.mkdir(path.dirname(curl)); grunt.file.mkdir(dest); grunt.log.ok('Downloading ' + src); - exec('curl ' + src + ' -o ' + curl + ' && cd ' + dest + ' && gzip -dc ' + curl + ' | tar -xf -', function (err) { + execFile('curl', [src, '-o', curl], function (err) { if (err) { throw err; } + grunt.log.ok('Downloaded ' + curl + ', extracting . . .'); + execFile('tar', ['-xzf', curl], { cwd: dest }, function (err) { + if (err) { throw err; } - grunt.log.ok('Downloaded ' + src); - - done(); + grunt.log.ok('Extracted ' + dest); + done(); + }); }); }); -}; \ No newline at end of file +}; diff --git a/tasks/data-zdump.js b/tasks/data-zdump.js index c177195..65314b5 100644 --- a/tasks/data-zdump.js +++ b/tasks/data-zdump.js @@ -1,14 +1,12 @@ "use strict"; var path = require('path'), - exec = require('child_process').exec; + execFile = require('child_process').execFile; module.exports = function (grunt) { grunt.registerTask('data-zdump', '3. Dump data with zdump(8).', function (version) { version = version || 'latest'; - console.log(path.resolve('zdump')); - var done = this.async(), zicBase = path.resolve('temp/zic', version), zdumpBase = path.resolve('temp/zdump', version), @@ -34,7 +32,7 @@ module.exports = function (grunt) { src = path.join(zicBase, file), dest = path.join(zdumpBase, file); - exec('zdump -v ' + src, { maxBuffer: 20*1024*1024 }, function (err, stdout) { + execFile('zdump', ['-v', src], { maxBuffer: 20*1024*1024 }, function (err, stdout) { if (err) { throw err; } grunt.file.mkdir(path.dirname(dest)); @@ -42,7 +40,7 @@ module.exports = function (grunt) { if (stdout.length === 0) { // on some systems, when there are no transitions then we have // to get creative to learn the offset and abbreviation - exec('zdump UTC ' + src, { maxBuffer: 20*1024*1024 }, function (_err, _stdout) { + execFile('zdump', ['UTC', src], { maxBuffer: 20*1024*1024 }, function (_err, _stdout) { if (_err) { throw _err; } grunt.file.write(dest + '.zdump', normalizePaths(_stdout)); diff --git a/tasks/data-zic.js b/tasks/data-zic.js index bd81f7e..7913f16 100644 --- a/tasks/data-zic.js +++ b/tasks/data-zic.js @@ -1,7 +1,7 @@ "use strict"; var path = require('path'), - exec = require('child_process').exec; + execFile = require('child_process').execFile; module.exports = function (grunt) { grunt.registerTask('data-zic', '2. Compile data sources with zic(8).', function (version) { @@ -22,7 +22,10 @@ module.exports = function (grunt) { var file = files.shift(), src = path.resolve('temp/download', version, file); - exec('zic -d ' + dest + ' ' + src, function (err) { + if (!grunt.file.exists(src)) { + throw new Error("Can't process " + src + " with zic. File doesn't exist"); + } + execFile('zic', ['-d', dest, src], function (err) { if (err) { throw err; } grunt.verbose.ok('Compiled zic ' + version + ':' + file);