CVE-2022-42725 (published 2022-10-10T05:15Z)

Warpinator through 1.2.14 allows access outside of an intended directory, as demonstrated by symbolic directory links.

CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N)

Weakness information:
cwe_id: CWE-59
cwe_name: Improper Link Resolution Before File Access ('Link Following')
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.
extended_description: 
            
            
         
         

Fix information:
repo_url: https://github.com/linuxmint/warpinator
commit_details: repo_url: https://github.com/linuxmint/warpinator
author: Michael Webster
author_date: 2022-09-18 10:15:50-04:00
msg: transfers: Don't allow a received file to be saved outside of the
transfer folder.

Since relative paths are valid for GFiles, we need to make sure
they resolve as descendants of the incoming transfer folder.
repository: repo_url: https://github.com/linuxmint/warpinator
repo_name: linuxmint/warpinator
description: Share files across the LAN
repo_language: C++
owner: linuxmint
file_changes: filename: transfers.py
change_type: ModificationType.MODIFY
diff: @@ -4,6 +4,7 @@ import os
 import logging
 import stat
 import shutil
+import gettext
 
 from gi.repository import GLib, Gio, GObject
 
@@ -12,6 +13,8 @@ from util import FileType
 import prefs
 import warp_pb2
 
+_ = gettext.gettext
+
 FILE_INFOS = ",".join([
     "standard::size",
     "standard::allocated-size",
@@ -164,6 +167,7 @@ class FileReceiver(GObject.Object):
     def __init__(self, op):
         super(FileReceiver, self).__init__()
         self.save_path = prefs.get_save_path()
+        self.save_path_file = Gio.File.new_for_path(self.save_path)
         self.op = op
         self.preserve_perms = prefs.preserve_permissions() and util.save_folder_is_native_fs()
         self.preserve_timestamp = prefs.preserve_timestamp() and util.save_folder_is_native_fs()
@@ -208,6 +212,9 @@ class FileReceiver(GObject.Object):
 
         if not self.current_gfile:
             self.current_gfile = Gio.File.new_for_path(path)
+            # Check for valid path (GFile resolves paths upon creation).
+            if self.save_path_file.get_relative_path(self.current_gfile) is None:
+                raise Exception(_("Resolved path is not valid: %s -> %s") % (path, self.current_gfile.get_path()))
 
         if s.file_type == FileType.DIRECTORY:
             os.makedirs(path, exist_ok=True)

programming_language: Python
repo_url: https://github.com/linuxmint/warpinator
commit_details: repo_url: https://github.com/linuxmint/warpinator
author: Michael Webster
author_date: 2022-09-20 15:06:04-04:00
msg: 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.
repository: repo_url: https://github.com/linuxmint/warpinator
repo_name: linuxmint/warpinator
description: Share files across the LAN
repo_language: C++
owner: linuxmint
file_changes: filename: transfers.py
change_type: ModificationType.MODIFY
diff: @@ -180,18 +180,17 @@ class FileReceiver(GObject.Object):
         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
@@ -222,8 +221,7 @@ class FileReceiver(GObject.Object):
             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

programming_language: Python
repo_url: https://github.com/linuxmint/warpinator
commit_details: repo_url: https://github.com/linuxmint/warpinator
author: Michael Webster
author_date: 2022-09-20 18:57:06-04:00
msg: transfers: Use Pathlib.Path instead of GFile for path safety check.

Pathlib.Path will resolve both links and relative paths to their
absolute paths - GFile treated links as real paths, so directory
links could potentially escape the incoming folder.

Follow-up to 5244c33d4c1
repository: repo_url: https://github.com/linuxmint/warpinator
repo_name: linuxmint/warpinator
description: Share files across the LAN
repo_language: C++
owner: linuxmint
file_changes: filename: transfers.py
change_type: ModificationType.MODIFY
diff: @@ -5,6 +5,7 @@ import logging
 import stat
 import shutil
 import gettext
+from pathlib import Path
 
 from gi.repository import GLib, Gio, GObject
 
@@ -167,7 +168,7 @@ class FileReceiver(GObject.Object):
     def __init__(self, op):
         super(FileReceiver, self).__init__()
         self.save_path = prefs.get_save_path()
-        self.save_path_file = Gio.File.new_for_path(self.save_path)
+        self.save_path_obj = Path(self.save_path).resolve()
         self.op = op
         self.preserve_perms = prefs.preserve_permissions() and util.save_folder_is_native_fs()
         self.preserve_timestamp = prefs.preserve_timestamp() and util.save_folder_is_native_fs()
@@ -210,10 +211,12 @@ class FileReceiver(GObject.Object):
             self.current_mtime_usec = s.time.mtime_usec
 
         if not self.current_gfile:
+            # Check for valid path (pathlib.Path resolves both relative and symbolically-linked paths)
+            test_path = Path(path).resolve()
+            if not test_path.is_relative_to(self.save_path_obj):
+                raise Exception(_("Resolved path is not valid: %s -> %s") % (path, str(test_path)))
+
             self.current_gfile = Gio.File.new_for_path(path)
-            # Check for valid path (GFile resolves paths upon creation).
-            if self.save_path_file.get_relative_path(self.current_gfile) is None:
-                raise Exception(_("Resolved path is not valid: %s -> %s") % (path, self.current_gfile.get_path()))
 
         if s.file_type == FileType.DIRECTORY:
             os.makedirs(path, exist_ok=True)

programming_language: Python
repo_url: https://github.com/linuxmint/warpinator
commit_details: repo_url: https://github.com/linuxmint/warpinator
author: Michael Webster
author_date: 2022-09-20 14:08:08-04:00
msg: transfers: Use g_file_query_exists() instead of g_file_test().

When checking for existing files, g_file_test (and os.path.exists)
both follow symbolic links, whereas g_file_query_exists() notices
the link itself.

In both cases the link (and not the link's target) would have been
overwritten, but now the recipient will know about it.
repository: repo_url: https://github.com/linuxmint/warpinator
repo_name: linuxmint/warpinator
description: Share files across the LAN
repo_language: C++
owner: linuxmint
file_changes: filename: util.py
change_type: ModificationType.MODIFY
diff: @@ -263,7 +263,8 @@ def files_exist(base_names):
     for name in base_names:
         path = os.path.join(prefs.get_save_path(), name)
         logging.debug("(server side) Checking if file or folder %s already exists." % (path,))
-        if GLib.file_test(path, GLib.FileTest.EXISTS):
+        file = Gio.File.new_for_path(path)
+        if file.query_exists(None):
             return True
 
     return False

programming_language: Python