mirror of
https://github.com/apple/container.git
synced 2026-10-25 08:08:29 +00:00
Prevent recursion attacks in EXT4Formatter.unlink (#415)
This issue doesn't affect any of our existing products. This is a preemptive fix for downstream consumers of EXT4.format where, in some platforms, leading `//` in the path could get resolved into a FileTree that looks like this ```sh / └── / └── usr ```
This commit is contained in:
1 parent
de075fff8a
commit
df125a2300
5 files changed
+282
-30
No files matched your search
@@ -199,42 +199,63 @@ extension EXT4 {
|
||||
//
|
||||
// - `path`: The `FilePath` specifying the path of the file or directory to delete.
|
||||
public func unlink(path: FilePath, directoryWhiteout: Bool = false) throws {
|
||||
guard let pathPtr = self.tree.lookup(path: path) else {
|
||||
guard let pathPtr = self.tree.lookup(path: path),
|
||||
let parentPtr = self.tree.lookup(path: path.dir)
|
||||
else {
|
||||
// We are being asked to unlink something that does not exist. Ignore
|
||||
return
|
||||
}
|
||||
let pathNode = pathPtr.pointee
|
||||
let inodeNumber = Int(pathNode.inode) - 1
|
||||
let pathInodePtr = self.inodes[inodeNumber]
|
||||
var pathInode = pathInodePtr.pointee
|
||||
let pathInode = pathInodePtr.pointee
|
||||
|
||||
if directoryWhiteout && !pathInode.mode.isDir() {
|
||||
throw Error.notDirectory(path)
|
||||
}
|
||||
|
||||
for childPtr in pathNode.children {
|
||||
try self.unlink(path: path.join(childPtr.pointee.name))
|
||||
// Iterative breath-first traversal of the FileTree to prevent recursion attacks
|
||||
var queue: [(parent: Ptr<FileTree.FileTreeNode>, entry: Ptr<FileTree.FileTreeNode>)] = pathNode.children.map { (pathPtr, $0) }
|
||||
var head: Int = 0
|
||||
while head < queue.count {
|
||||
let currNode = queue[head].entry
|
||||
for childPtr in currNode.pointee.children {
|
||||
queue.append((currNode, childPtr))
|
||||
}
|
||||
head += 1
|
||||
}
|
||||
|
||||
for (parent, entry) in queue.reversed() {
|
||||
try _unlink(parentNodePtr: parent, pathNodePtr: entry)
|
||||
}
|
||||
|
||||
guard !directoryWhiteout else {
|
||||
return
|
||||
}
|
||||
|
||||
if let parentNodePtr = self.tree.lookup(path: path.dir) {
|
||||
let parentNode = parentNodePtr.pointee
|
||||
let parentInodePtr = self.inodes[Int(parentNode.inode) - 1]
|
||||
var parentInode = parentInodePtr.pointee
|
||||
if pathInode.mode.isDir() {
|
||||
if parentInode.linksCount > 2 {
|
||||
parentInode.linksCount -= 1
|
||||
}
|
||||
try _unlink(parentNodePtr: parentPtr, pathNodePtr: pathPtr)
|
||||
}
|
||||
|
||||
private func _unlink(parentNodePtr: Ptr<FileTree.FileTreeNode>, pathNodePtr: Ptr<FileTree.FileTreeNode>) throws {
|
||||
let pathNode = pathNodePtr.pointee
|
||||
let pathComponent = pathNode.name
|
||||
let inodeNumber = Int(pathNode.inode) - 1
|
||||
let pathInodePtr = self.inodes[inodeNumber]
|
||||
var pathInode = pathInodePtr.pointee
|
||||
|
||||
let parentNode = parentNodePtr.pointee
|
||||
let parentInodePtr = self.inodes[Int(parentNode.inode) - 1]
|
||||
var parentInode = parentInodePtr.pointee
|
||||
if pathInode.mode.isDir() {
|
||||
if parentInode.linksCount > 2 {
|
||||
parentInode.linksCount -= 1
|
||||
}
|
||||
parentInodePtr.pointee = parentInode
|
||||
parentNode.children.removeAll { childPtr in
|
||||
childPtr.pointee.name == path.base
|
||||
}
|
||||
parentNodePtr.pointee = parentNode
|
||||
}
|
||||
parentInodePtr.pointee = parentInode
|
||||
parentNode.children.removeAll { childPtr in
|
||||
childPtr.pointee.name == pathComponent
|
||||
}
|
||||
parentNodePtr.pointee = parentNode
|
||||
|
||||
if let hardlink = pathNode.link {
|
||||
// the file we are deleting is a hardlink, decrement the link count
|
||||
@@ -352,6 +373,10 @@ extension EXT4 {
|
||||
guard mode.isLink() else { // unless it is a link, then it can be replaced by a dir
|
||||
throw Error.notFile(path)
|
||||
}
|
||||
// root cannot be replaced with a link
|
||||
if path.isRoot {
|
||||
throw Error.unsupportedFiletype
|
||||
}
|
||||
}
|
||||
try self.unlink(path: path)
|
||||
}
|
||||
|
||||
@@ -49,6 +49,10 @@ extension FilePath {
|
||||
self.components.map { $0.string }
|
||||
}
|
||||
|
||||
public var isRoot: Bool { // platform agnostic
|
||||
self.removingRoot().isEmpty
|
||||
}
|
||||
|
||||
public init(_ url: URL) {
|
||||
self.init(url.path(percentEncoded: false))
|
||||
}
|
||||
|
||||
@@ -71,12 +71,8 @@ public enum Endianness {
|
||||
|
||||
// returns current endianness
|
||||
public var Endian: Endianness {
|
||||
switch CFByteOrderGetCurrent() {
|
||||
case CFByteOrder(CFByteOrderLittleEndian.rawValue):
|
||||
return .little
|
||||
case CFByteOrder(CFByteOrderBigEndian.rawValue):
|
||||
return .big
|
||||
default:
|
||||
fatalError("impossible")
|
||||
var value: UInt32 = 0x0102_0304
|
||||
return withUnsafeBytes(of: &value) { buffer in
|
||||
buffer.first == 0x04 ? .little : .big
|
||||
}
|
||||
}
|
||||
@@ -330,12 +330,7 @@ extension Socket {
|
||||
|
||||
var cmsgBuf = [UInt8](repeating: 0, count: Int(CZ_CMSG_SPACE(Int(MemoryLayout<Int32>.size))))
|
||||
msg.msg_control = withUnsafeMutablePointer(to: &cmsgBuf[0]) { UnsafeMutableRawPointer($0) }
|
||||
|
||||
#if canImport(Glibc)
|
||||
msg.msg_controllen = size_t(cmsgBuf.count)
|
||||
#else
|
||||
msg.msg_controllen = socklen_t(cmsgBuf.count)
|
||||
#endif
|
||||
msg.msg_controllen = numericCast(cmsgBuf.count)
|
||||
|
||||
let recvResult = withUnsafeMutablePointer(to: &msg) { msgPtr in
|
||||
sysRecvmsg(handle.fileDescriptor, msgPtr, 0)
|
||||
|
||||
Reference in new issue
Block a user