Skip to content

Commit 407d8ad

Browse files
committed
improve symlink resoltion, copy safety
Signed-off-by: Zen <[email protected]>
1 parent 94ed11a commit 407d8ad

File tree

1 file changed

+20
-6
lines changed

1 file changed

+20
-6
lines changed

src/ugrd/generator_helpers.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,14 @@ def _write(self, file_name: Union[Path, str], contents: list[str], chmod_mask=0o
100100
self.logger.debug("[%s] Set file permissions: %s" % (file_path, chmod_mask))
101101

102102
def _copy(self, source: Union[Path, str], dest=None) -> None:
103-
"""Copies a file into the initramfs build directory."""
103+
"""Copies a file into the initramfs build directory.
104+
If a destination is not provided, the source is used, under the build directory.
105+
106+
If the destination parent is a symlink, the symlink is resolved.
107+
Crates parent directories if they do not exist
108+
109+
Raises a RuntimeError if the destination path is not within the build directory.
110+
"""
104111
from shutil import copy2
105112

106113
if not isinstance(source, Path):
@@ -111,10 +118,12 @@ def _copy(self, source: Union[Path, str], dest=None) -> None:
111118
dest = source
112119

113120
dest_path = self._get_build_path(dest)
121+
build_base = self._get_build_path("/")
114122

115123
while dest_path.parent.is_symlink():
116-
self.logger.debug("Resolving symlink: %s" % dest_path.parent)
117-
dest_path = self._get_build_path(dest_path.parent.resolve() / dest_path.name)
124+
resolved_path = dest_path.parent.resolve() / dest_path.name
125+
self.logger.debug("Resolved symlink: %s -> %s" % (dest_path.parent, resolved_path))
126+
dest_path = self._get_build_path(resolved_path)
118127

119128
if not dest_path.parent.is_dir():
120129
self.logger.debug("Parent directory for '%s' does not exist: %s" % (dest_path.name, dest_path.parent))
@@ -126,6 +135,11 @@ def _copy(self, source: Union[Path, str], dest=None) -> None:
126135
self.logger.debug("Destination is a directory, adding source filename: %s" % source.name)
127136
dest_path = dest_path / source.name
128137

138+
try: # Ensure the target is in the build directory
139+
dest_path.relative_to(build_base)
140+
except ValueError as e:
141+
raise RuntimeError("Destination path is not within the build directory: %s" % dest_path) from e
142+
129143
self.logger.log(self["_build_log_level"], "Copying '%s' to '%s'" % (source, dest_path))
130144
copy2(source, dest_path)
131145

@@ -144,7 +158,7 @@ def _symlink(self, source: Union[Path, str], target: Union[Path, str]) -> None:
144158
target = self._get_build_path(target)
145159

146160
while target.parent.is_symlink():
147-
self.logger.debug("Resolving target symlink: %s" % target.parent)
161+
self.logger.debug("Resolving target parent symlink: %s" % target.parent)
148162
target = self._get_build_path(target.parent.resolve() / target.name)
149163

150164
if not target.parent.is_dir():
@@ -153,9 +167,9 @@ def _symlink(self, source: Union[Path, str], target: Union[Path, str]) -> None:
153167

154168
build_source = self._get_build_path(source)
155169
while build_source.parent.is_symlink():
156-
self.logger.debug("Resolving source symlink: %s" % build_source.parent)
170+
self.logger.debug("Resolving source parent symlink: %s" % build_source.parent)
157171
build_source = self._get_build_path(build_source.parent.resolve() / build_source.name)
158-
source = build_source.relative_to(self._get_build_path("/"))
172+
source = build_source.relative_to(self._get_build_path("/"))
159173

160174
if target.is_symlink():
161175
if target.resolve() == source:

0 commit comments

Comments
 (0)