mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +02:00
fix: never allow the projects directory to become a project directory
Loading a .gns3 placed directly in the projects root registered the shared projects directory as the project path (load_project derives the path from the file's parent directory). Deleting such an entry ran rmtree on the projects directory itself, wiping every project until a root-owned file stopped it, and left a zombie entry in the controller. Three layers of protection: - Controller.load_project() refuses a .gns3 whose parent directory is the projects directory; the normal subdirectory layout is unaffected - the Project.path setter rejects the projects directory itself and its ancestors, closing the same hole for POST/PUT with an explicit path - Project.delete() uses realpath + commonpath instead of commonprefix: entries whose path is the projects root are refused, and sibling directories sharing a string prefix (/srv/projects-evil vs /srv/projects) are no longer treated as inside the projects dir Also removes the project_load MCP tool: loading by raw server filesystem path is a footgun for automated clients; projects can still be opened by project_id via the remaining tools.
This commit is contained in:
11 files changed
+125
-28
No files matched your search
@@ -471,6 +471,18 @@ class Project:
|
||||
@path.setter
|
||||
def path(self, path):
|
||||
check_path_allowed(path)
|
||||
|
||||
# The projects directory itself (or one of its ancestors) must
|
||||
# never become a project directory: deleting such a "project"
|
||||
# would wipe every project on the controller.
|
||||
real_path = os.path.realpath(path)
|
||||
real_projects_path = os.path.realpath(get_default_project_directory())
|
||||
if os.path.commonpath([real_path, real_projects_path]) == real_path:
|
||||
raise ControllerForbiddenError(
|
||||
f"The project directory cannot be '{path}': it must be a subdirectory "
|
||||
f"of '{real_projects_path}', not the projects directory itself or one of its parents"
|
||||
)
|
||||
|
||||
try:
|
||||
os.makedirs(path, exist_ok=True)
|
||||
except OSError as e:
|
||||
@@ -1608,11 +1620,19 @@ class Project:
|
||||
await self._cleanup_web_wireshark_container()
|
||||
|
||||
try:
|
||||
project_directory = get_default_project_directory()
|
||||
if not os.path.commonprefix([project_directory, self.path]) == project_directory:
|
||||
project_directory = os.path.realpath(get_default_project_directory())
|
||||
path = os.path.realpath(self.path)
|
||||
if os.path.commonpath([path, project_directory]) != project_directory:
|
||||
raise ControllerError(
|
||||
f"Project '{self._name}' cannot be deleted because it is not in the default project directory: '{project_directory}'"
|
||||
)
|
||||
if path == project_directory:
|
||||
# A poisoned or hand-crafted entry whose path is the
|
||||
# projects root itself must never be deletable: rmtree
|
||||
# would wipe every project on the controller.
|
||||
raise ControllerError(
|
||||
f"Project '{self._name}' cannot be deleted because its directory is the projects directory itself: '{path}'"
|
||||
)
|
||||
shutil.rmtree(self.path)
|
||||
except OSError as e:
|
||||
raise ControllerError(f"Cannot delete project directory {self.path}: {str(e)}")
|
||||
|
||||
Reference in new issue
Block a user