CVE-2022-42725: Improper Link Resolution Before File Access ('Link Following') (CWE-59)
value: Warpinator through 1.2.14 allows access outside of an intended directory, as demonstrated by symbolic directory links.
CWE description: The product attempts to access a file based on the filename, but it does not properly prevent that filename from identifying a link or shortcut that resolves to an unintended resource.
CVSS v3 base score: 7.5
Published: 2022-10-10T05:15Z
Repository: https://github.com/linuxmint/warpinator
Language: Python
Files changed: src/transfers.py
Commit date: 2022-09-20 15:49:41 -0400
Commit message: transfers: Always remove existing files before beginning a transfer.

The user must approve overwriting, or they've must have allowed it
via preferences. Either way, if we've made it this far, we can get
rid of existing files before starting the transfer.

This eliminates the risk of any existing symbolic directory links
potentially being followed outside of the download directory, which
would not be detected by 5244c33d4c109.

Diff: diff --git a/src/transfers.py b/src/transfers.py
index 34f4ba2..5075cb5 100644
--- a/src/transfers.py
+++ b/src/transfers.py
@@ -178,22 +178,21 @@ class FileReceiver(GObject.Object):
         self.current_stream = None
         self.current_mode = 0
         self.current_mtime = 0
         self.current_mtime_usec = 0
 
-        if op.existing:
-            for name in op.top_dir_basenames:
-                try:
-                    path = os.path.join(self.save_path, name)
-                    if os.path.isdir(path): # file not found is ok
-                        shutil.rmtree(path)
-                    else:
-                        os.remove(path)
-                except FileNotFoundError:
-                    pass
-                except Exception as e:
-                    logging.warning("Problem removing existing files.  Transfer may not succeed: %s" % e)
+        for name in op.top_dir_basenames:
+            try:
+                path = os.path.join(self.save_path, name)
+                if os.path.isdir(path): # file not found is ok
+                    shutil.rmtree(path)
+                else:
+                    os.remove(path)
+            except FileNotFoundError:
+                pass
+            except Exception as e:
+                logging.warning("Problem removing existing files.  Transfer may not succeed: %s" % e)
 
         # We write files top-down.  If we're preserving permissions and we receive
         # a folder in some hierarchy that is not writable, we won't be able to create
         # anything inside it.
         self.folder_permission_change_list = []
@@ -220,12 +219,11 @@ class FileReceiver(GObject.Object):
             os.makedirs(path, exist_ok=True)
         elif s.file_type == FileType.SYMBOLIC_LINK:
             make_symbolic_link(self.op, path, s.symlink_target)
         else:
             if self.current_stream == None:
-                flags = Gio.FileCreateFlags.REPLACE_DESTINATION
-                self.current_stream = self.current_gfile.replace(None, False, flags, None)
+                self.current_stream = self.current_gfile.create(Gio.FileCreateFlags.NONE, None)
 
             if not s.chunk:
                 return
 
             self.current_stream.write_bytes(GLib.Bytes(s.chunk), None)

Vulnerable code:         if op.existing:
            for name in op.top_dir_basenames:
                try:
                    path = os.path.join(self.save_path, name)
                    if os.path.isdir(path): # file not found is ok
                        shutil.rmtree(path)
                    else:
                        os.remove(path)
                except FileNotFoundError:
                    pass
                except Exception as e:
                    logging.warning("Problem removing existing files.  Transfer may not succeed: %s" % e)
                flags = Gio.FileCreateFlags.REPLACE_DESTINATION
                self.current_stream = self.current_gfile.replace(None, False, flags, None)
Fixed code:         for name in op.top_dir_basenames:
            try:
                path = os.path.join(self.save_path, name)
                if os.path.isdir(path): # file not found is ok
                    shutil.rmtree(path)
                else:
                    os.remove(path)
            except FileNotFoundError:
                pass
            except Exception as e:
                logging.warning("Problem removing existing files.  Transfer may not succeed: %s" % e)
                self.current_stream = self.current_gfile.create(Gio.FileCreateFlags.NONE, None)