mirror of
https://github.com/apple/container.git
synced 2026-08-24 10:05:43 -05:00
Adds client uid validation to XPC server. (#896)
- When a user performs an `su` the effective UID changes but the bootstrap mach port does not, so that if container is running as `alice` from a GUI login session, it's possible to `su bob` and continue running container. While this doesn't pose a significant security risk as it's necessary for Alice to know Bob's password and manually enter it with `su`, this change closes the loophole by validating that client UID from the caller's audit token matches that of the API server.
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#if os(macOS)
|
||||
import CAuditToken
|
||||
import ContainerizationError
|
||||
import Foundation
|
||||
import Logging
|
||||
@@ -25,7 +26,7 @@ public struct XPCServer: Sendable {
|
||||
public typealias RouteHandler = @Sendable (XPCMessage) async throws -> XPCMessage
|
||||
|
||||
private let routes: [String: RouteHandler]
|
||||
// Access to `connection` is protected by a lock
|
||||
// Access to `connection` is protected by a lock.
|
||||
private nonisolated(unsafe) let connection: xpc_connection_t
|
||||
private let lock = NSLock()
|
||||
|
||||
@@ -112,8 +113,10 @@ public struct XPCServer: Sendable {
|
||||
cont.finish()
|
||||
}
|
||||
if !(replySent.withLock({ $0 }) && object.connectionClosed) {
|
||||
// When a xpc connection is closed, the framework sends a final XPC_ERROR_CONNECTION_INVALID message.
|
||||
// We can ignore this if we know we have already handled the request.
|
||||
// When a xpc connection is closed, the framework sends
|
||||
// a final XPC_ERROR_CONNECTION_INVALID message.
|
||||
// We can ignore this if we know we have already handled
|
||||
// the request.
|
||||
self.log.error("xpc client handler connection error \(object.errorDescription ?? "no description")")
|
||||
}
|
||||
default:
|
||||
@@ -145,24 +148,63 @@ public struct XPCServer: Sendable {
|
||||
}
|
||||
|
||||
func handleMessage(connection: xpc_connection_t, object: xpc_object_t) async throws {
|
||||
// All requests are dictionary-valued.
|
||||
guard xpc_get_type(object) == XPC_TYPE_DICTIONARY else {
|
||||
log.error("invalid request - not a dictionary")
|
||||
Self.replyWithError(
|
||||
connection: connection,
|
||||
object: object,
|
||||
err: ContainerizationError(.invalidArgument, message: "invalid request")
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure that the client has our EUID
|
||||
var token = audit_token_t()
|
||||
xpc_dictionary_get_audit_token(object, &token)
|
||||
let serverEuid = geteuid()
|
||||
let clientEuid = audit_token_to_euid(token)
|
||||
guard clientEuid == serverEuid else {
|
||||
log.error(
|
||||
"unauthorized request - uid mismatch",
|
||||
metadata: [
|
||||
"server_euid": "\(serverEuid)",
|
||||
"client_euid": "\(clientEuid)",
|
||||
])
|
||||
Self.replyWithError(
|
||||
connection: connection,
|
||||
object: object,
|
||||
err: ContainerizationError(.invalidState, message: "unauthorized request")
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
guard let route = object.route else {
|
||||
log.error("empty route")
|
||||
log.error("invalid request - empty route")
|
||||
Self.replyWithError(
|
||||
connection: connection,
|
||||
object: object,
|
||||
err: ContainerizationError(.invalidArgument, message: "invalid request")
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if let handler = routes[route] {
|
||||
let message = XPCMessage(object: object)
|
||||
do {
|
||||
let message = XPCMessage(object: object)
|
||||
let response = try await handler(message)
|
||||
xpc_connection_send_message(connection, response.underlying)
|
||||
} catch let error as ContainerizationError {
|
||||
let reply = message.reply()
|
||||
log.error("handler for \(route) threw error \(error)")
|
||||
reply.set(error: error)
|
||||
xpc_connection_send_message(connection, reply.underlying)
|
||||
Self.replyWithError(
|
||||
connection: connection,
|
||||
object: object,
|
||||
err: error
|
||||
)
|
||||
} catch {
|
||||
let reply = message.reply()
|
||||
log.error("handler for \(route) threw error \(error)")
|
||||
let message = XPCMessage(object: object)
|
||||
let reply = message.reply()
|
||||
|
||||
// Check if this is a VolumeError by looking at the error description
|
||||
let errorMessage = error.localizedDescription
|
||||
@@ -178,6 +220,13 @@ public struct XPCServer: Sendable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func replyWithError(connection: xpc_connection_t, object: xpc_object_t, err: ContainerizationError) {
|
||||
let message = XPCMessage(object: object)
|
||||
let reply = message.reply()
|
||||
reply.set(error: err)
|
||||
xpc_connection_send_message(connection, reply.underlying)
|
||||
}
|
||||
}
|
||||
|
||||
extension xpc_object_t {
|
||||
|
||||
Reference in New Issue
Block a user