From ae58bbd5ca6b03c9ac964f945b7487de29487fcd Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Thu, 30 Jul 2026 14:09:34 -0700 Subject: [PATCH 01/10] sysutils: Add a test for remove and symlinked directories This tests to make sure that using remove on a symlink pointing to a directory just removes the symlink, not the whole directory. Note that shutil.rmtree has used a symlink resistant version since python 3.3 so this test is purely to demonstrate that remove() uses unlink in that case. (cherry picked from commit ad558e033a88555fd0b5b8156d4fcc184ef78725) --- tests/pylorax/test_sysutils.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/pylorax/test_sysutils.py b/tests/pylorax/test_sysutils.py index 5edf3f4f1..b22b0dfbb 100644 --- a/tests/pylorax/test_sysutils.py +++ b/tests/pylorax/test_sysutils.py @@ -19,6 +19,7 @@ import tempfile import os +from pylorax.executils import execWithRedirect from pylorax.sysutils import joinpaths, touch, replace, chown_, chmod_, remove, linktree from pylorax.sysutils import _read_file_end @@ -69,6 +70,20 @@ def test_remove(self): remove(remove_file) self.assertFalse(os.path.exists(remove_file)) + def test_remove_symlink_dir(self): + # Make sure remove doesn't removed symlinked directories + with tempfile.TemporaryDirectory() as tdname: + test_file = os.path.join(tdname, "lorax-test-file") + with open(test_file, "w", encoding="UTF-8") as f: + f.write("test was here") + rc = execWithRedirect("/bin/ln", ["-s", tdname, "/var/tmp/lorax-test-link"]) + self.assertEqual(0, rc) + + remove("/var/tmp/lorax-test-link") + + self.assertTrue(os.path.exists(tdname)) + self.assertFalse(os.path.exists("/var/tmp/lorax-test-link")) + def test_linktree(self): with tempfile.TemporaryDirectory() as tdname: path = os.path.join("one", "two", "three") From fa9f93d37c584c94f9524c6b85ff6d14c18438af Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Thu, 30 Jul 2026 14:26:10 -0700 Subject: [PATCH 02/10] novirt_install: Use remove on /tmp paths and /mnt/sysimage Just in case they have already been created as symlinks, use remove instead of shutil which will raise an error. (cherry picked from commit e82825d17b7142b848afa3a666b1a5537bab7edc) --- src/pylorax/installer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pylorax/installer.py b/src/pylorax/installer.py index f0679fa98..d1536be93 100644 --- a/src/pylorax/installer.py +++ b/src/pylorax/installer.py @@ -33,7 +33,7 @@ from pylorax.imgutils import mkqemu_img, mktar, mkcpio, mkfsimage_from_disk from pylorax.monitor import LogMonitor from pylorax.mount import IsoMountpoint -from pylorax.sysutils import joinpaths +from pylorax.sysutils import joinpaths, remove from pylorax.treebuilder import udev_escape @@ -422,8 +422,8 @@ def novirt_install(opts, disk_img, disk_size, cancel_func=None, tar_img=None): # Clean up /tmp/ from previous runs to prevent stale info from being used for path in ["/tmp/yum.repos.d/", "/tmp/yum.cache/"]: - if os.path.isdir(path): - shutil.rmtree(path) + if os.path.exists(path): + remove(path) args = ["--kickstart", opts.ks[0], "--cmdline"] if opts.anaconda_args: @@ -445,7 +445,7 @@ def novirt_install(opts, disk_img, disk_size, cancel_func=None, tar_img=None): elif opts.make_tar or opts.make_oci: # Install under dirinstall_path, make sure it starts clean if os.path.exists(dirinstall_path): - shutil.rmtree(dirinstall_path) + remove(dirinstall_path) if opts.make_oci: # OCI installs under /rootfs/ From 76e144562beb224cb8d8c4624fe8a0550a98e9fe Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Thu, 30 Jul 2026 15:58:53 -0700 Subject: [PATCH 03/10] findkernels: Exclude kernel symlinks pointing outside root Make sure that all the kernels returned are under the root directory. This prevents potential issues with accessing files on the host system. (cherry picked from commit 728071dec1cd74f956225e3f110af9cd5c7d3be5) --- src/pylorax/treebuilder.py | 7 ++++- tests/pylorax/test_treebuilder.py | 46 ++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/pylorax/treebuilder.py b/src/pylorax/treebuilder.py index cb3d6b965..6bb1d083b 100644 --- a/src/pylorax/treebuilder.py +++ b/src/pylorax/treebuilder.py @@ -28,7 +28,7 @@ import libdnf5 as dnf5 from libdnf5.common import QueryCmp_EQ as EQ -from pylorax.sysutils import joinpaths, remove +from pylorax.sysutils import joinpaths, safe_joinpaths, remove from pylorax.base import DataHolder from pylorax.ltmpl import LoraxTemplateRunner import pylorax.imgutils as imgutils @@ -417,6 +417,11 @@ def findkernels(root="/", kdir="boot"): kernels = [] bootfiles = os.listdir(joinpaths(root, kdir)) for f in bootfiles: + # Exclude files that are symlinks pointing outside of root + try: + _ = safe_joinpaths(root, kdir, f) + except RuntimeError: + continue match = kre.match(f) if match: kernel = DataHolder(path=joinpaths(kdir, f)) diff --git a/tests/pylorax/test_treebuilder.py b/tests/pylorax/test_treebuilder.py index 833fcbfbe..638ef552c 100644 --- a/tests/pylorax/test_treebuilder.py +++ b/tests/pylorax/test_treebuilder.py @@ -23,7 +23,9 @@ from pylorax import ArchData, DataHolder from pylorax.dnfbase import get_dnf_base_object -from pylorax.treebuilder import RuntimeBuilder +from pylorax.executils import execWithRedirect +from pylorax.sysutils import joinpaths +from pylorax.treebuilder import RuntimeBuilder, findkernels # TODO Put these into a common test library location @contextmanager @@ -147,3 +149,45 @@ def test_skip_branding(self): branding = self.install_branding(repo_dir, skip_branding=True) self.assertEqual(branding.release, None) self.assertEqual(branding.logos, None) + + +class FindkernelsTestCase(unittest.TestCase): + def test_findkernels(self): + with tempfile.TemporaryDirectory(prefix="lorax.test.root.") as root_dir: + os.makedirs(joinpaths(root_dir, "boot")) + + # Make some fake kernel files in the temporary boot dir + for n in ["vmlinuz-7.0.0-100.fc43.x86_64", + "initramfs-7.0.0-100.fc43.x86_64.img", + "vmlinuz-7.0.1-100.fc43.x86_64", + "initramfs-7.0.0-100.fc43.x86_64.img"]: + with open(joinpaths(root_dir, "boot", n), "w", encoding="UTF-8") as f: + f.write("lorax test fake file") + + # Make a symlink to one of them that's relative + rc = execWithRedirect("/bin/ln", + ["-s", "./vmlinuz-7.0.1-100.fc43.x86_64", + joinpaths(root_dir, "boot", "vmlinuz-7.0.1-101.fc43.x86_64")]) + self.assertEqual(0, rc) + + # Make a symlink pointing outside root_dir + rc = execWithRedirect("/bin/ln", + ["-s", "../../../etc/fstab", + joinpaths(root_dir, "boot", "vmlinuz-7.0.1-102.fc43.x86_64")]) + self.assertEqual(0, rc) + + # Make an absolute symlink + rc = execWithRedirect("/bin/ln", + ["-s", "/etc/some-file", + joinpaths(root_dir, "boot", "vmlinuz-7.0.1-103.fc43.x86_64")]) + self.assertEqual(0, rc) + + # call findkernels on it + kernels = findkernels(root_dir, "boot") + kernel_versions = sorted([k.version for k in kernels]) + + # List includes kernel files plus symlink pointing inside the mount + # Does not include vmlinuz-7.0.1-102.fc43.x86_64 which points outside + self.assertEqual(["7.0.0-100.fc43.x86_64", + "7.0.1-100.fc43.x86_64", + "7.0.1-101.fc43.x86_64"], kernel_versions) From a03c20ec05e47c6b7c75ccad58ae69cb330296a8 Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Fri, 31 Jul 2026 09:50:55 -0700 Subject: [PATCH 04/10] treebuilder: Check for symlink when writing module-info If there is an existing module-info, and it is a symlink, remove it before writing the new one. (cherry picked from commit cc6741cefbfab1f7c829cb668e8fa34ea2ef0854) --- src/pylorax/treebuilder.py | 14 +++++++++----- tests/pylorax/test_treebuilder.py | 21 ++++++++++++++++++++- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/pylorax/treebuilder.py b/src/pylorax/treebuilder.py index 6bb1d083b..734e72eaf 100644 --- a/src/pylorax/treebuilder.py +++ b/src/pylorax/treebuilder.py @@ -58,11 +58,15 @@ def read_module_set(name): (name, _ext) = os.path.splitext(mod) # foo.ko -> (foo, .ko) desc = module_desc(joinpaths(root,mod)) or "%s driver" % name modinfo.append(dict(name=name, type=modtype, desc=desc)) - - out = open(outfile or joinpaths(moddir,"module-info"), "w") - out.write("Version 0\n") - for mod in sorted(modinfo, key=lambda m: m.get('name')): - out.write('{name}\n\t{type}\n\t"{desc:.65}"\n'.format(**mod)) + _write_modinfo(modinfo, outfile or joinpaths(moddir,"module-info")) + +def _write_modinfo(modinfo, outfile): + if os.path.islink(outfile): + os.unlink(outfile) + with open(outfile, "w", encoding="UTF-8") as out: + out.write("Version 0\n") + for mod in sorted(modinfo, key=lambda m: m.get('name')): + out.write('{name}\n\t{type}\n\t"{desc:.65}"\n'.format(**mod)) class RuntimeBuilder(object): '''Builds the anaconda runtime image. diff --git a/tests/pylorax/test_treebuilder.py b/tests/pylorax/test_treebuilder.py index 638ef552c..0364073cb 100644 --- a/tests/pylorax/test_treebuilder.py +++ b/tests/pylorax/test_treebuilder.py @@ -25,7 +25,7 @@ from pylorax.dnfbase import get_dnf_base_object from pylorax.executils import execWithRedirect from pylorax.sysutils import joinpaths -from pylorax.treebuilder import RuntimeBuilder, findkernels +from pylorax.treebuilder import RuntimeBuilder, findkernels, _write_modinfo # TODO Put these into a common test library location @contextmanager @@ -191,3 +191,22 @@ def test_findkernels(self): self.assertEqual(["7.0.0-100.fc43.x86_64", "7.0.1-100.fc43.x86_64", "7.0.1-101.fc43.x86_64"], kernel_versions) + +class ModInfoTestCase(unittest.TestCase): + def test_write_modinfo(self): + modinfo = [{"name": "foo", "type": "scsi", "desc": "foo driver"}] + + with tempfile.TemporaryDirectory(prefix="lorax.test.root.") as moddir: + with open(joinpaths(moddir, "other-file"), "w", encoding="UTF-8") as f: + f.write("lorax test file") + + rc = execWithRedirect("/bin/ln", + ["-s", joinpaths(moddir, "other-file"), + joinpaths(moddir, "module-info")]) + self.assertEqual(0, rc) + + _write_modinfo(modinfo, joinpaths(moddir, "module-info")) + + # check symlinked file's content to make sure it is undisturbed + data = open(joinpaths(moddir, "other-file"), "r", encoding="UTF-8").read() + self.assertEqual("lorax test file", data) From de96719eeda348d2c133293572d2d00bd714b644 Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Tue, 4 Aug 2026 16:28:34 -0700 Subject: [PATCH 05/10] ltmpl: Add tests for access outside outroot This helps catch mistakes with hardlink, symlink, copy, move trying to access paths outside of the outroot set in the template runner. Should help prevent accidentally accessing host files and paths. (cherry picked from commit a051c3b3f9baaf441ff8060f5749a62d1fb8d2f7) --- tests/pylorax/templates/bad-copy-cmd.tmpl | 3 +++ tests/pylorax/templates/bad-hardlink-cmd.tmpl | 3 +++ tests/pylorax/templates/bad-move-cmd.tmpl | 3 +++ .../templates/bad-move-symlink-cmd.tmpl | 4 ++++ tests/pylorax/templates/bad-symlink-cmd.tmpl | 3 +++ tests/pylorax/test_ltmpl.py | 23 +++++++++++++++++++ 6 files changed, 39 insertions(+) create mode 100644 tests/pylorax/templates/bad-copy-cmd.tmpl create mode 100644 tests/pylorax/templates/bad-hardlink-cmd.tmpl create mode 100644 tests/pylorax/templates/bad-move-cmd.tmpl create mode 100644 tests/pylorax/templates/bad-move-symlink-cmd.tmpl create mode 100644 tests/pylorax/templates/bad-symlink-cmd.tmpl diff --git a/tests/pylorax/templates/bad-copy-cmd.tmpl b/tests/pylorax/templates/bad-copy-cmd.tmpl new file mode 100644 index 000000000..999f3f677 --- /dev/null +++ b/tests/pylorax/templates/bad-copy-cmd.tmpl @@ -0,0 +1,3 @@ +<%page /> +append /lorax-file "A text file" +copy /lorax-file ../../var/tmp/copied-file diff --git a/tests/pylorax/templates/bad-hardlink-cmd.tmpl b/tests/pylorax/templates/bad-hardlink-cmd.tmpl new file mode 100644 index 000000000..a1fe65774 --- /dev/null +++ b/tests/pylorax/templates/bad-hardlink-cmd.tmpl @@ -0,0 +1,3 @@ +<%page /> +append /lorax-file "A hardlinked file" +hardlink /lorax-file ../../var/tmp/linked-file diff --git a/tests/pylorax/templates/bad-move-cmd.tmpl b/tests/pylorax/templates/bad-move-cmd.tmpl new file mode 100644 index 000000000..d2c124515 --- /dev/null +++ b/tests/pylorax/templates/bad-move-cmd.tmpl @@ -0,0 +1,3 @@ +<%page /> +append /lorax-file "A text file" +move /lorax-file ../../var/tmp/moved-file diff --git a/tests/pylorax/templates/bad-move-symlink-cmd.tmpl b/tests/pylorax/templates/bad-move-symlink-cmd.tmpl new file mode 100644 index 000000000..31083cbe8 --- /dev/null +++ b/tests/pylorax/templates/bad-move-symlink-cmd.tmpl @@ -0,0 +1,4 @@ +<%page /> +append /lorax-file "A text file" +symlink /var/tmp/linked-file /symlinked-file-2 +move /lorax-file /symlinked-file-2 diff --git a/tests/pylorax/templates/bad-symlink-cmd.tmpl b/tests/pylorax/templates/bad-symlink-cmd.tmpl new file mode 100644 index 000000000..983f3bb32 --- /dev/null +++ b/tests/pylorax/templates/bad-symlink-cmd.tmpl @@ -0,0 +1,3 @@ +<%page /> +append /lorax-file "A symlinked file" +symlink /lorax-file ../../var/tmp/existing-file diff --git a/tests/pylorax/test_ltmpl.py b/tests/pylorax/test_ltmpl.py index d98cfcc5d..a79824eef 100644 --- a/tests/pylorax/test_ltmpl.py +++ b/tests/pylorax/test_ltmpl.py @@ -321,22 +321,45 @@ def test_hardlink(self): self.assertTrue(os.path.exists(joinpaths(self.root_dir, "/linked-file"))) self.assertTrue(os.path.exists(joinpaths(self.root_dir, "/lorax-dir/lorax-file"))) + def test_bad_hardlink(self): + """Test a hardlink template command pointing outside the outroot""" + with self.assertRaises(RuntimeError): + self.runner.run("bad-hardlink-cmd.tmpl") + def test_symlink(self): """Test symlink template command""" self.runner.run("symlink-cmd.tmpl") self.assertTrue(os.path.islink(joinpaths(self.root_dir, "/symlinked-file"))) + def test_bad_symlink(self): + """Test symlink template command pointing outside the outroot""" + with self.assertRaises(RuntimeError): + self.runner.run("bad-symlink-cmd.tmpl") + def test_copy(self): """Test copy template command""" self.runner.run("copy-cmd.tmpl") self.assertTrue(os.path.exists(joinpaths(self.root_dir, "/copied-file"))) + def test_bad_copy(self): + """Test copy template command pointing outside the outroot""" + with self.assertRaises(RuntimeError): + self.runner.run("bad-copy-cmd.tmpl") + def test_move(self): """Test move template command""" self.runner.run("move-cmd.tmpl") self.assertFalse(os.path.exists(joinpaths(self.root_dir, "/lorax-file"))) self.assertTrue(os.path.exists(joinpaths(self.root_dir, "/moved-file"))) + def test_bad_move(self): + """Test move template command pointing outside the outroot""" + with self.assertRaises(RuntimeError): + self.runner.run("bad-move-cmd.tmpl") + + with self.assertRaises(RuntimeError): + self.runner.run("bad-move-symlink-cmd.tmpl") + def test_remove(self): """Test remove template command""" self.runner.run("remove-cmd.tmpl") From a2154f0f22e40ae2deb644f576913f07d4b9db5b Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Wed, 5 Aug 2026 13:38:53 -0700 Subject: [PATCH 06/10] sysutils: Add safe_joinpaths function This function is similar to joinpaths, except that it will evaluate the final path and raise a RuntimeError if it is outside of the first argument passed. This can be used to help prevent absolute symlinks and directory traversals from pointing outside of a directory tree. Includes tests. (cherry picked from commit 60144eecdde390900b7489657eacabd632812581) --- src/pylorax/sysutils.py | 12 ++++++++++++ tests/pylorax/test_sysutils.py | 22 +++++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/pylorax/sysutils.py b/src/pylorax/sysutils.py index 35f8b0fe8..981d738b2 100644 --- a/src/pylorax/sysutils.py +++ b/src/pylorax/sysutils.py @@ -44,6 +44,18 @@ def joinpaths(*args, **kwargs): return path +def safe_joinpaths(*args, **kwargs): + """ safe_joinpaths joins paths and checks for an escape + and raises RuntimeError if the final path is not under the first. + + NOTE: The path must exist in order to be properly evaluated + """ + path = joinpaths(*args, follow_symlinks=True) + if not path.startswith(args[0]): + raise RuntimeError(f"path does not start with {args[0]}") + return path + + def touch(fname): with open(fname, "w") as f: f.write("") diff --git a/tests/pylorax/test_sysutils.py b/tests/pylorax/test_sysutils.py index b22b0dfbb..cc4f148ec 100644 --- a/tests/pylorax/test_sysutils.py +++ b/tests/pylorax/test_sysutils.py @@ -21,7 +21,7 @@ from pylorax.executils import execWithRedirect from pylorax.sysutils import joinpaths, touch, replace, chown_, chmod_, remove, linktree -from pylorax.sysutils import _read_file_end +from pylorax.sysutils import safe_joinpaths, _read_file_end class SysUtilsTest(unittest.TestCase): def test_joinpaths(self): @@ -35,6 +35,26 @@ def test_joinpaths(self): self.assertEqual(joinpaths(tdname, "link-file", follow_symlinks=True), os.path.join(tdname, "real-file")) + def test_safe_joinpaths(self): + with tempfile.TemporaryDirectory() as tdname: + self.assertEqual(safe_joinpaths(tdname, "foo", "bar", "baz"), tdname+"/foo/bar/baz") + + with open(os.path.join(tdname, "real-file"), "w") as f: + f.write("lorax test file") + os.symlink(os.path.join(tdname, "real-file"), os.path.join(tdname, "link-file")) + + self.assertEqual(safe_joinpaths(tdname, "link-file"), os.path.join(tdname, "real-file")) + + def test_safe_joinpaths_bad(self): + with tempfile.TemporaryDirectory() as tdname: + os.symlink("/var/tmp/some-file", os.path.join(tdname, "link-file")) + + with self.assertRaises(RuntimeError): + _ = safe_joinpaths(tdname, "link-file") + + with self.assertRaises(RuntimeError): + _ = safe_joinpaths(tdname, "../../", "link-file") + def test_touch(self): touch_file="/var/tmp/lorax-test-touch-file" touch(touch_file) From 923b3b11ea96329afce571330952770ce426f9df Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Tue, 4 Aug 2026 11:33:46 -0700 Subject: [PATCH 07/10] ltmpl: Check for paths inside outroot on hardlink, symlink, copy, move This will raise a RuntimeError if the real path points outside the template runner's outroot. NOTE: This does not guarantee the safety of the template -- this is running as root, it has access to the whole system and is not safe to pass unknown templates into. This change is meant to help prevent accidentally accessing the host files. (cherry picked from commit 5a908f5dddb98193911ac7eea7bd5075208b378c) --- src/pylorax/ltmpl.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/pylorax/ltmpl.py b/src/pylorax/ltmpl.py index 6d3d715b2..1578b54a0 100644 --- a/src/pylorax/ltmpl.py +++ b/src/pylorax/ltmpl.py @@ -28,7 +28,7 @@ from subprocess import CalledProcessError import shutil -from pylorax.sysutils import joinpaths, cpfile, mvfile, replace, remove +from pylorax.sysutils import joinpaths, safe_joinpaths, cpfile, mvfile, replace, remove from pylorax.dnfhelper import LoraxDownloadCallback, LoraxRpmCallback from pylorax.base import DataHolder from pylorax.executils import runcmd, runcmd_output @@ -413,9 +413,18 @@ def __init__(self, inroot, outroot, dbo=None, fatalerrors=True, def _out(self, path): return joinpaths(self.outroot, path) + def _in(self, path): return joinpaths(self.inroot, path) + def _insideout(self, path): + """ Return true if path is really inside outroot """ + try: + _ = safe_joinpaths(self.outroot, path) + except RuntimeError: + return False + return True + def _filelist(self, *pkg_specs): """ Return the list of files in the packages matching the globs """ # libdnf5's filter_installed query will not work unless the base it reset and reloaded. @@ -671,6 +680,13 @@ def hardlink(self, src, dest): ''' if isdir(self._out(dest)): dest = joinpaths(dest, basename(src)) + + if not self._insideout(src): + raise RuntimeError(f"paths outside outroot not allowed on {src}") + + if not self._insideout(dest): + raise RuntimeError(f"paths outside outroot not allowed on {dest}") + os.link(self._out(src), self._out(dest)) def symlink(self, target, dest): @@ -678,6 +694,9 @@ def symlink(self, target, dest): symlink SRC DEST Create a symlink at DEST which points to SRC. ''' + if not self._insideout(dest): + raise RuntimeError(f"paths outside outroot not allowed on {dest}") + if rexists(self._out(dest)): self.remove(dest) os.symlink(target, self._out(dest)) @@ -690,6 +709,12 @@ def copy(self, src, dest): If DEST doesn't exist, SRC will be copied to a file with that name, if the path leading to it exists. ''' + if not self._insideout(src): + raise RuntimeError(f"paths outside outroot not allowed on {src}") + + if not self._insideout(dest): + raise RuntimeError(f"paths outside outroot not allowed on {dest}") + try: cpfile(self._out(src), self._out(dest)) except shutil.Error as e: @@ -700,6 +725,12 @@ def move(self, src, dest): move SRC DEST Move SRC to DEST. ''' + if not self._insideout(src): + raise RuntimeError(f"paths outside outroot not allowed on {src}") + + if not self._insideout(dest): + raise RuntimeError(f"paths outside outroot not allowed on {dest}") + mvfile(self._out(src), self._out(dest)) def remove(self, *fileglobs): From 6dc348324009334cec88265ef5e02b688117896e Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Wed, 5 Aug 2026 14:30:20 -0700 Subject: [PATCH 08/10] creator: Use safe_joinpaths in make_live_images This prevents absolute symlinks in the ostree boot path from pointing outside the image's directory tree. (cherry picked from commit eecafdb4fe71f77829d781ad13a16b447ffd066c) --- src/pylorax/creator.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pylorax/creator.py b/src/pylorax/creator.py index 20cd0593e..b0502e2fb 100644 --- a/src/pylorax/creator.py +++ b/src/pylorax/creator.py @@ -44,7 +44,7 @@ from pylorax.installer import novirt_install, virt_install, InstallError from pylorax.treebuilder import TreeBuilder, RuntimeBuilder from pylorax.treebuilder import findkernels -from pylorax.sysutils import joinpaths, remove +from pylorax.sysutils import joinpaths, remove, safe_joinpaths # Default parameters for rebuilding initramfs, override with --dracut-arg or --dracut-conf @@ -590,10 +590,10 @@ def make_live_images(opts, work_dir, disk_img): log.info("Rebuilding initramfs for live") with Mount(rootfs_img, opts="loop") as mnt_dir: try: - mount(joinpaths(mnt_dir, "boot"), opts="bind", mnt=joinpaths(mnt_dir, sys_root, "boot")) - rebuild_initrds_for_live(opts, joinpaths(mnt_dir, sys_root), work_dir) + mount(joinpaths(mnt_dir, "boot"), opts="bind", mnt=safe_joinpaths(mnt_dir, sys_root, "boot")) + rebuild_initrds_for_live(opts, safe_joinpaths(mnt_dir, sys_root), work_dir) finally: - umount(joinpaths(mnt_dir, sys_root, "boot"), delete=False) + umount(safe_joinpaths(mnt_dir, sys_root, "boot"), delete=False) remove(squashfs_root_dir) From 2e93664e33c11db2f2f0b5868cef12a4d6dc5f59 Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Wed, 5 Aug 2026 15:50:11 -0700 Subject: [PATCH 09/10] creator: Use safe_joinpaths in make_livecd This ensures that the path used for the config_files cannot point outside of the mount_dir. (cherry picked from commit 0a61cc17b673c266ffb96e138e66778ba58fb176) --- src/pylorax/creator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pylorax/creator.py b/src/pylorax/creator.py index b0502e2fb..56b24efff 100644 --- a/src/pylorax/creator.py +++ b/src/pylorax/creator.py @@ -362,7 +362,7 @@ def make_livecd(opts, mount_dir, work_dir): # I think these should be release specific, not from lorax, but for now configdir = joinpaths(opts.lorax_templates,"live/config_files/") configdir_path = "tmp/config_files" - fullpath = joinpaths(mount_dir, configdir_path) + fullpath = safe_joinpaths(mount_dir, configdir_path) if os.path.exists(fullpath): remove(fullpath) copytree(configdir, fullpath) From d40b14b9cb947cfb79f79a5f74b2bde9973fd145 Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Wed, 5 Aug 2026 15:52:09 -0700 Subject: [PATCH 10/10] creator: Use safe_joinpaths in mount_boot_part_over_root This ensures that the boot directory from the image cannot be a symlink pointing outside of the root_dir (cherry picked from commit 0e03b1ff70992fb46ac9cf3417fae219dbb4cf64) --- src/pylorax/creator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pylorax/creator.py b/src/pylorax/creator.py index 56b24efff..5077be0cc 100644 --- a/src/pylorax/creator.py +++ b/src/pylorax/creator.py @@ -405,7 +405,7 @@ def mount_boot_part_over_root(img_mount): mount("/dev/mapper/"+dev, mnt=tmp_mount_dir) if is_boot_part(tmp_mount_dir): umount(tmp_mount_dir) - sysroot_boot_dir = joinpaths(root_dir, "boot") + sysroot_boot_dir = safe_joinpaths(root_dir, "boot") mount("/dev/mapper/"+dev, mnt=sysroot_boot_dir) break else: