1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
|
import SystemExtras
import SystemPackage
struct PathResolution {
private let mode: FileDescriptor.AccessMode
private let options: FileDescriptor.OpenOptions
private let permissions: FilePermissions
private var baseFd: FileDescriptor
private let path: FilePath
private var openDirectories: [FileDescriptor]
/// Reverse-ordered remaining path components
private var components: FilePath.ComponentView
init(
baseDirFd: FileDescriptor,
mode: FileDescriptor.AccessMode,
options: FileDescriptor.OpenOptions,
permissions: FilePermissions,
path: FilePath
) {
self.baseFd = baseDirFd
self.mode = mode
self.options = options
self.permissions = permissions
self.path = path
self.openDirectories = []
self.components = FilePath.ComponentView(path.components.reversed())
}
mutating func parentDirectory() throws {
guard let lastDirectory = openDirectories.popLast() else {
// no more parent directory means too many `..`
throw WASIAbi.Errno.EPERM
}
self.baseFd = lastDirectory
}
mutating func regular(component: FilePath.Component) throws {
let options: FileDescriptor.OpenOptions
let mode: FileDescriptor.AccessMode
if !self.components.isEmpty {
var intermediateOtions: FileDescriptor.OpenOptions = []
// When trying to open an intermediate directory,
// we can assume it's directory.
intermediateOtions.insert(.directory)
// FIXME: Resolve symlink in safe way
intermediateOtions.insert(.noFollow)
options = intermediateOtions
mode = .readOnly
} else {
options = self.options
mode = self.mode
}
try WASIAbi.Errno.translatingPlatformErrno {
let newFd = try self.baseFd.open(
at: FilePath(root: nil, components: component),
mode, options: options, permissions: permissions
)
self.openDirectories.append(self.baseFd)
self.baseFd = newFd
}
}
mutating func resolve() throws -> FileDescriptor {
if path.isAbsolute {
// POSIX openat(2) interprets absolute path ignoring base directory fd
// but it leads sandbox-escaping, so reject absolute path here.
throw WASIAbi.Errno.EPERM
}
while let component = components.popLast() {
switch component.kind {
case .currentDirectory:
break // no-op
case .parentDirectory:
try parentDirectory()
case .regular: try regular(component: component)
}
}
return self.baseFd
}
}
extension SandboxPrimitives {
static func openAt(
start startFd: FileDescriptor,
path: FilePath,
mode: FileDescriptor.AccessMode,
options: FileDescriptor.OpenOptions,
permissions: FilePermissions
) throws -> FileDescriptor {
var resolution = PathResolution(
baseDirFd: startFd, mode: mode, options: options,
permissions: permissions, path: path
)
return try resolution.resolve()
}
}
|