CVE-2022-48682 (published 2024-04-26T01:15Z)

In deletefiles in FDUPES before 2.2.0, a TOCTOU race condition allows arbitrary file deletion via a symlink.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/adrianlopezroche/fdupes
commit_details: repo_url: https://github.com/adrianlopezroche/fdupes
author: Adrian Lopez
author_date: 2022-08-16 14:39:18-04:00
msg: Call stat() before deleting any file to ensure it's still the same file.
repository: repo_url: https://github.com/adrianlopezroche/fdupes
repo_name: adrianlopezroche/fdupes
description: FDUPES is a program for identifying or deleting duplicate files residing within specified directories.
repo_language: C
owner: adrianlopezroche
file_changes: filename: Makefile.am
change_type: ModificationType.MODIFY
diff: @@ -17,6 +17,8 @@ fdupes_SOURCES = fdupes.c\
  flags.h\
  confirmmatch.c\
  confirmmatch.h\
+ removeifnotchanged.c\
+ removeifnotchanged.h\
  mbstowcs_escape_invalid.c\
  mbstowcs_escape_invalid.h\
  md5/md5.c\
@@ -59,6 +61,8 @@ fdupes_SOURCES = fdupes.c\
  flags.h\
  confirmmatch.c\
  confirmmatch.h\
+ removeifnotchanged.c\
+ removeifnotchanged.h\
  mbstowcs_escape_invalid.c\
  mbstowcs_escape_invalid.h\
  positive_wcwidth.c\

programming_language: Shell
filename: fdupes.c
change_type: ModificationType.MODIFY
diff: @@ -49,6 +49,7 @@
 #include "log.h"
 #include "sigint.h"
 #include "flags.h"
+#include "removeifnotchanged.h"
 
 long long minsize = -1;
 long long maxsize = -1;
@@ -804,6 +805,7 @@ void deletefiles(file_t *files, int prompt, FILE *tty, char *logfile)
   FILE *file1;
   FILE *file2;
   int ismatch;
+  char *errorstring;
 
   curfile = files;
   
@@ -1005,7 +1007,7 @@ void deletefiles(file_t *files, int prompt, FILE *tty, char *logfile)
     }
 
     if (ismatch) {
-      if (remove(dupelist[x]->d_name) == 0) {
+      if (removeifnotchanged(dupelist[x], &errorstring) == 0) {
         printf("   [-] %s\n", dupelist[x]->d_name);
 
         if (loginfo)
@@ -1013,7 +1015,7 @@ void deletefiles(file_t *files, int prompt, FILE *tty, char *logfile)
       }
       else {
         printf("   [!] %s ", dupelist[x]->d_name);
-        printf("-- unable to delete file!\n");
+        printf("-- unable to delete file: %s!\n", errorstring);
 
         if (loginfo)
           log_file_remaining(loginfo, dupelist[x]->d_name);
@@ -1130,6 +1132,7 @@ void deletesuccessor(file_t **existing, file_t *duplicate, int matchconfirmed,
 {
   file_t *to_keep;
   file_t *to_delete;
+  char *errorstring;
 
   if (comparef(duplicate, *existing) >= 0)
   {
@@ -1156,14 +1159,14 @@ void deletesuccessor(file_t **existing, file_t *duplicate, int matchconfirmed,
 
   if (matchconfirmed)
   {
-    if (remove(to_delete->d_name) == 0) {
+    if (removeifnotchanged(to_delete, &errorstring) == 0) {
       printf("   [-] %s\n", to_delete->d_name);
 
       if (loginfo)
         log_file_deleted(loginfo, to_delete->d_name);
     } else {
       printf("   [!] %s ", to_delete->d_name);
-      printf("-- unable to delete file!\n");
+      printf("-- unable to delete file: %s!\n", errorstring);
 
       if (loginfo)
         log_file_remaining(loginfo, to_delete->d_name);

programming_language: C
filename: ncurses-commands.c
change_type: ModificationType.MODIFY
diff: @@ -29,6 +29,7 @@
 #include "wcs.h"
 #include "mbstowcs_escape_invalid.h"
 #include "log.h"
+#include "removeifnotchanged.h"
 #include <wchar.h>
 #include <pcre2.h>
 
@@ -780,7 +781,7 @@ int cmd_prune(struct filegroup *groups, int groupcount, wchar_t *commandargument
             ismatch = 1;
           }
 
-          if (ismatch && remove(groups[g].files[f].file->d_name) == 0)
+          if (ismatch && removeifnotchanged(groups[g].files[f].file, 0) == 0)
           {
             set_file_action(&groups[g].files[f], FILEACTION_DELIST, deletiontally);
 

programming_language: C
filename: removeifnotchanged.c
change_type: ModificationType.ADD
diff: @@ -0,0 +1,41 @@
+#include "removeifnotchanged.h"
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+int removeifnotchanged(const file_t *file, char **errorstring)
+{
+  int result;
+  struct stat st;
+
+  static char *filechanged = "File contents changed during processing";
+  static char *unknownerror = "Unknown error";
+
+  stat(file->d_name, &st);
+
+  if (file->device != st.st_dev ||
+      file->inode != st.st_ino ||
+      file->ctime != st.st_ctime ||
+      file->mtime != st.st_mtime ||
+      file->size != st.st_size)
+  {
+    if (errorstring != 0)
+        *errorstring = filechanged;
+
+    return -2;
+  }
+  else
+  {
+    result = remove(file->d_name);
+
+    if (result != 0 && errorstring != 0)
+    {
+      *errorstring = strerror(errno);
+
+      if (*errorstring == 0)
+        *errorstring = unknownerror;
+    }
+
+    return result;
+  }
+}

programming_language: C
filename: removeifnotchanged.h
change_type: ModificationType.ADD
diff: @@ -0,0 +1,8 @@
+#ifndef REMOVEIFNOTCHANGED_H
+#define REMOVEIFNOTCHANGED_H
+
+#include "fdupes.h"
+
+int removeifnotchanged(const file_t *file, char **errorstring);
+
+#endif
\ No newline at end of file

programming_language: Haskell