From b2be8dec67c6b8d4568c42b417c2afce7ead2070 Mon Sep 17 00:00:00 2001 From: Dmitry Kovba Date: Thu, 9 Apr 2026 11:09:07 -0700 Subject: [PATCH] Remove the incorrect check for visited inodes (#659) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes the incorrect check for visited inodes. Fixes the added failing tests: > ✘ Test sameAbsoluteSymlinkFollowedTwice() recorded an issue at TestEXT4Reader+IO.swift:502:6: Caught error: symlink loop while resolving: target/../symlink/file.txt > ✘ Test sameAbsoluteSymlinkFollowedTwice() failed after 0.009 seconds with 1 issue. > ✘ Test sameRelativeSymlinkFollowedTwice() recorded an issue at TestEXT4Reader+IO.swift:516:6: Caught error: symlink loop while resolving: ../target/../symlink/file.txt > ✘ Test sameRelativeSymlinkFollowedTwice() failed after 0.010 seconds with 1 issue. --- .../ContainerizationEXT4/EXT4Reader+IO.swift | 7 ----- .../TestEXT4Reader+IO.swift | 28 +++++++++++++++++++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/Sources/ContainerizationEXT4/EXT4Reader+IO.swift b/Sources/ContainerizationEXT4/EXT4Reader+IO.swift index f9daf25c..c2c93641 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 { diff --git a/Tests/ContainerizationEXT4Tests/TestEXT4Reader+IO.swift b/Tests/ContainerizationEXT4Tests/TestEXT4Reader+IO.swift index d987d9a0..6023a2b7 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