CVE-2022-1537 (published 2022-05-10T14:15Z)

file.copy operations in GruntJS are vulnerable to a TOCTOU race condition leading to arbitrary file write in GitHub repository gruntjs/grunt prior to 1.5.3. This vulnerability is capable of arbitrary file writes which can lead to local privilege escalation to the GruntJS user if a lower-privileged user has write access to both source and destination directories as the lower-privileged user can create a symlink to the GruntJS user's .bashrc file or replace /etc/shadow file if the GruntJS user is root.

Severity: MEDIUM
CVSS v2 base score: 6.9 (AV:L/AC:M/Au:N/C:C/I:C/A:C)
CVSS v3 base score: 7.0, HIGH (CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-367
cwe_name: Time-of-check Time-of-use (TOCTOU) Race Condition
description: The product checks the state of a resource before using that resource, but the resource's state can change between the check and the use in a way that invalidates the results of the check. This can cause the product to perform invalid actions when the resource is in an unexpected state.
extended_description: This weakness can be security-relevant when an attacker can influence the state of the resource between check and use. This can happen with shared resources such as files, memory, or even variables in multithreaded programs.
         

Fix information:
repo_url: https://github.com/gruntjs/grunt
commit_details: repo_url: https://github.com/gruntjs/grunt
author: Vlad Filippov
author_date: 2022-04-23 16:28:10-04:00
msg: Patch up race condition in symlink copying.
repository: repo_url: https://github.com/gruntjs/grunt
repo_name: gruntjs/grunt
description: Grunt: The JavaScript Task Runner
repo_language: JavaScript
owner: gruntjs
file_changes: filename: CHANGELOG
change_type: ModificationType.MODIFY
diff: @@ -1,3 +1,7 @@
+v1.5.3
+  date: 2022-04-23
+  changes:
+    - Patch up race condition in symlink copying.
 v1.5.2
   date: 2022-04-12
   changes:

programming_language: Markdown
filename: file.js
change_type: ModificationType.MODIFY
diff: @@ -294,11 +294,6 @@ file.write = function(filepath, contents, options) {
 // processing content, writing output.
 // Handles symlinks by coping them as files or directories.
 file.copy = function copy(srcpath, destpath, options) {
-  if (file.isLink(destpath)) {
-    // in case destpath is a symlink, avoid following the symlink, instead overwrite it later
-    fs.unlinkSync(destpath);
-  }
-
   if (file.isLink(srcpath)) {
     file._copySymbolicLink(srcpath, destpath);
   } else if (file.isDir(srcpath)) {
@@ -338,8 +333,8 @@ file._copy = function(srcpath, destpath, options) {
     }
   }
   // Abort copy if the process function returns false.
-  if (contents === false) {
-    grunt.verbose.writeln('Write aborted.');
+  if (contents === false || file.isLink(destpath)) {
+    grunt.verbose.writeln('Write aborted. Either the process function returned false or the destination is a symlink');
   } else {
     file.write(destpath, contents, readWriteOptions);
   }

programming_language: JavaScript
filename: file_test.js
change_type: ModificationType.MODIFY
diff: @@ -916,5 +916,13 @@ exports.file = {
       test.ok(fs.lstatSync(path.join(destdir.path, path.basename(fixtures))).isSymbolicLink());
       test.done();
     },
-  }
+  },
+  'symbolicLinkDestError': function(test) {
+    test.expect(1);
+    var tmpfile = new Tempdir();
+    fs.symlinkSync(path.resolve('test/fixtures/octocat.png'), path.join(tmpfile.path, 'octocat.png'), 'file');
+    grunt.file.copy(path.resolve('test/fixtures/octocat.png'), path.join(tmpfile.path, 'octocat.png'));
+    test.ok(fs.lstatSync(path.join(tmpfile.path, 'octocat.png')).isSymbolicLink());
+    test.done();
+  },
 };

programming_language: JavaScript