项目文件夹

文件
wehub-resource-sync 680845cb1c
Build containerization / Verify commit signatures (push) Has been skipped
Build containerization / containerization (push) Successful in 0s
Linux build / Determine Swift version (push) Has been cancelled
Linux build / Linux compile check (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:25:30 +08:00

233 行
7.7 KiB
Swift

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
//===----------------------------------------------------------------------===//
// Copyright © 2026 Apple Inc. and the Containerization project authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//
//
import ContainerizationArchive
import ContainerizationEXT4
import Foundation
import SystemPackage
import Testing
struct EXT4WhiteoutTests {
private func makeTempFileURL(prefix: String) throws -> URL {
let base = FileManager.default.temporaryDirectory
let url = base.appendingPathComponent("\(prefix)-\(UUID().uuidString)")
return url
}
private func writeLayerWithOpaqueWhiteout(to url: URL) throws {
let writer = try ArchiveWriter(
format: .pax,
filter: .gzip,
file: url
)
let ts = Date()
let entry = WriteEntry()
entry.modificationDate = ts
entry.creationDate = ts
entry.owner = 0
entry.group = 0
entry.fileType = .directory
entry.permissions = 0o755
entry.path = "usr"
try writer.writeEntry(entry: entry, data: nil)
entry.path = "usr/local"
try writer.writeEntry(entry: entry, data: nil)
entry.path = "usr/local/bin"
try writer.writeEntry(entry: entry, data: nil)
entry.fileType = .regular
entry.permissions = 0o644
let fooData = Data("hello\n".utf8)
entry.path = "usr/local/bin/foo"
entry.size = Int64(fooData.count)
try writer.writeEntry(entry: entry, data: fooData)
entry.fileType = .regular
entry.permissions = 0o000
entry.size = 0
entry.path = "usr//.wh..wh..opq"
try writer.writeEntry(entry: entry, data: nil)
try writer.finishEncoding()
}
private func withFormatter<T>(
prefix: String = "ext4-whiteout",
blockSize: UInt32 = 4096,
minDiskSize: UInt64 = 16.mib(),
_ body: (EXT4.Formatter, FilePath) async throws -> T
) async throws -> T {
let imageURL = try makeTempFileURL(prefix: prefix)
let imagePath = FilePath(imageURL.path)
defer {
try? FileManager.default.removeItem(at: imageURL)
}
let formatter = try EXT4.Formatter(
imagePath,
blockSize: blockSize,
minDiskSize: minDiskSize
)
let result = try await body(formatter, imagePath)
return result
}
@Test
func unpack_with_opaque_whiteout_path_does_not_stack_overflow_and_cleans_directory() async throws {
let layerURL = try makeTempFileURL(prefix: "ext4-wh-layer")
defer {
try? FileManager.default.removeItem(at: layerURL)
}
try writeLayerWithOpaqueWhiteout(to: layerURL)
try await withFormatter { formatter, imagePath in
try await formatter.unpack(
source: FilePath(layerURL.path).url,
format: .pax,
compression: .gzip,
progress: nil
)
try formatter.close()
let reader = try EXT4.EXT4Reader(blockDevice: FilePath(imagePath.description))
#expect(reader.exists(FilePath("/usr/local/bin")) == false)
#expect(reader.exists(FilePath("/usr/local/bin/foo")) == false)
}
}
@Test
func directoryWhiteout_from_wh_opq_path_with_repeated_slashes_terminates() async throws {
try await withFormatter { formatter, _ in
try formatter.create(
path: FilePath("/usr"),
mode: EXT4.Inode.Mode(.S_IFDIR, 0o755)
)
try formatter.create(
path: FilePath("/usr/local"),
mode: EXT4.Inode.Mode(.S_IFDIR, 0o755)
)
try formatter.create(
path: FilePath("/usr/local/bin"),
mode: EXT4.Inode.Mode(.S_IFDIR, 0o755)
)
try formatter.create(
path: FilePath("/usr/local/bin/foo"),
mode: EXT4.Inode.Mode(.S_IFREG, 0o644)
)
try formatter.create(
path: FilePath("/usr/local/bin/bar"),
mode: EXT4.Inode.Mode(.S_IFREG, 0o644)
)
let whiteoutEntry = FilePath("//usr//.wh..wh..opq")
let directoryToWhiteout = whiteoutEntry.dir
let normalized = directoryToWhiteout.lexicallyNormalized()
#expect(normalized == FilePath("/usr"))
try formatter.unlink(path: directoryToWhiteout, directoryWhiteout: true)
}
}
/// Test the exact recursion attack sequence:
/// create /_d
/// create symlink / -> /_
/// create /_
/// create symlink / -> /_
///
/// This creates a recursive symlink structure that can cause infinite recursion
/// during directory traversal operations.
@Test
func recursion_attack_sequence_does_not_cause_infinite_recursion() async throws {
await #expect(throws: EXT4.Formatter.Error.unsupportedFiletype) {
try await withFormatter { formatter, _ in
// Step 1: create /_d
try formatter.create(
path: FilePath("/_d"),
mode: EXT4.Inode.Mode(.S_IFDIR, 0o755)
)
// Step 2: create symlink / -> /_
try formatter.create(
path: FilePath("/"),
link: FilePath("/_"),
mode: EXT4.Inode.Mode(.S_IFLNK, 0o777)
)
try formatter.create(
path: FilePath("/_"),
mode: EXT4.Inode.Mode(.S_IFDIR, 0o755)
)
try formatter.create(
path: FilePath("/"),
link: FilePath("/_"),
mode: EXT4.Inode.Mode(.S_IFLNK, 0o777)
)
}
}
}
@Test
func file_whiteouts_and_directory_whiteouts_interact_correctly() async throws {
try await withFormatter { formatter, imagePath in
// Lowerlayer content
try formatter.create(
path: FilePath("/opt"),
mode: EXT4.Inode.Mode(.S_IFDIR, 0o755)
)
try formatter.create(
path: FilePath("/opt/app"),
mode: EXT4.Inode.Mode(.S_IFDIR, 0o755)
)
try formatter.create(
path: FilePath("/opt/app/cache"),
mode: EXT4.Inode.Mode(.S_IFDIR, 0o755)
)
try formatter.create(
path: FilePath("/opt/app/cache/file"),
mode: EXT4.Inode.Mode(.S_IFREG, 0o644)
)
try formatter.unlink(path: FilePath("/opt/app/cache/file"))
try formatter.unlink(
path: FilePath("/opt/app/cache"),
directoryWhiteout: true
)
try formatter.close()
let reader = try EXT4.EXT4Reader(blockDevice: FilePath(imagePath.description))
#expect(reader.exists(FilePath("/opt")))
#expect(reader.exists(FilePath("/opt/app")))
#expect(reader.exists(FilePath("/opt/app/cache")))
#expect(reader.exists(FilePath("/opt/app/cache/file")) == false)
}
}
}