From 51f9c45c6390277262d64b3896a5d75e28ef172d Mon Sep 17 00:00:00 2001 From: Dmitry Kovba Date: Tue, 7 Apr 2026 10:01:32 -0700 Subject: [PATCH 1/2] Add tests for valid symlinks followed twice --- .../TestEXT4Reader+IO.swift | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Tests/ContainerizationEXT4Tests/TestEXT4Reader+IO.swift b/Tests/ContainerizationEXT4Tests/TestEXT4Reader+IO.swift index 5a773952..9ae5c8b9 100644 --- a/Tests/ContainerizationEXT4Tests/TestEXT4Reader+IO.swift +++ b/Tests/ContainerizationEXT4Tests/TestEXT4Reader+IO.swift @@ -499,6 +499,34 @@ struct EXT4PathIOTests { } } + @Test + func sameAbsoluteSymlinkFollowedTwice() throws { + let url = try buildFS { fmt in + try self.createDir(fmt, "/target") + try self.createFile(fmt, "/target/file.txt", "OK") + try self.createSymlink(fmt, "/symlink", "/target") + } + defer { try? FileManager.default.removeItem(at: url) } + + let r = try openReader(url) + let data = try r.readFile(at: FilePath("/symlink/../symlink/file.txt")) + #expect(String(decoding: data, as: UTF8.self) == "OK") + } + + @Test + func sameRelativeSymlinkFollowedTwice() throws { + let url = try buildFS { fmt in + try self.createDir(fmt, "/target") + try self.createFile(fmt, "/target/file.txt", "OK") + try self.createSymlink(fmt, "/symlink", "../target") + } + defer { try? FileManager.default.removeItem(at: url) } + + let r = try openReader(url) + let data = try r.readFile(at: FilePath("/symlink/../symlink/file.txt")) + #expect(String(decoding: data, as: UTF8.self) == "OK") + } + @Test func boundsCheckingForInvalidExtents() throws { // This test verifies that the reader properly validates extent addresses From f3cecb5fd966d9ddd1b9dbf0edb470520b266c56 Mon Sep 17 00:00:00 2001 From: Dmitry Kovba Date: Tue, 7 Apr 2026 10:02:06 -0700 Subject: [PATCH 2/2] Remove the incorrect check for visited inodes --- Sources/ContainerizationEXT4/EXT4Reader+IO.swift | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Sources/ContainerizationEXT4/EXT4Reader+IO.swift b/Sources/ContainerizationEXT4/EXT4Reader+IO.swift index 4b7fdc92..cc5fdf6f 100644 --- a/Sources/ContainerizationEXT4/EXT4Reader+IO.swift +++ b/Sources/ContainerizationEXT4/EXT4Reader+IO.swift @@ -281,7 +281,6 @@ extension EXT4.EXT4Reader { var parentStack: [EXT4.InodeNumber] = [] // Track parent chain for proper ".." handling var symlinkHops = 0 - var visitedInodes = Set() // Process components one at a time to handle symlinks in the middle of paths var componentIndex = 0 @@ -330,12 +329,6 @@ extension EXT4.EXT4Reader { // Check if child is a symlink let childInode = try getInode(number: child.1) if childInode.mode.isLink() && followSymlinks { - // Check for symlink loop - if visitedInodes.contains(child.1) { - throw EXT4.PathIOError.symlinkLoop(FilePath(components.joined(separator: "/")).description) - } - visitedInodes.insert(child.1) - // Enforce max symlink depth symlinkHops += 1 if symlinkHops > maxSymlinks {