Fix container system status false negative under a Seatbelt sandbox. (#2316)

- `launchctl list` does not work as expected in the sandbox.
- Probe with `launchctl print <domain>/<label>` instead,
  which launchd does allow a sandboxed process, and key
  off exit codes: 0 means registered, 113 (ENOSERVICE)
  means not registered, and anything else is an error rather
  than being mistranslated into a negative.
- The probe is advisory in `system status`, so an indeterminate
  answer falls through to the XPC health check, whose success 
  is authoritative.

Signed-off-by: Renlord Yang <renlord@apple.com>
This commit is contained in:
Renlord Yang authored and GitHub committed 2026-10-05 15:52:37 -07:00
1 parent 8ba7e2a65a
commit 175e493a6f
3 files changed
+95 -3

No files matched your search

@@ -42,7 +42,8 @@ extension Application {
public init() {}
public func run() async throws {
let isRegistered = try ServiceManager.isRegistered(fullServiceLabel: "\(prefix)apiserver")
let domain = try ServiceManager.getDomainString()
let isRegistered = (try? ServiceManager.isRegistered(fullServiceLabel: "\(domain)/\(prefix)apiserver")) ?? true
if !isRegistered {
try Output.render(payload: StatusPayload(status: "unregistered"), format: format) {
"apiserver is not running and not registered with launchd"
+38 -2
View File
@@ -18,6 +18,10 @@ import ContainerizationError
import Foundation
public struct ServiceManager {
enum LaunchctlStatus {
static let noSuchService: Int32 = 113
}
private static func runLaunchctlCommand(args: [String]) throws -> Int32 {
let launchctl = Foundation.Process()
launchctl.executableURL = URL(fileURLWithPath: "/bin/launchctl")
@@ -94,8 +98,40 @@ public struct ServiceManager {
/// Check if a service has been registered or not.
public static func isRegistered(fullServiceLabel label: String) throws -> Bool {
let exitStatus = try runLaunchctlCommand(args: ["list", label])
return exitStatus == 0
let result = try runLaunchctlPrint(target: label)
return try Self.interpretPrintStatus(result.status, target: label, standardError: result.standardError)
}
private static func runLaunchctlPrint(target: String) throws -> (status: Int32, standardError: String) {
let launchctl = Foundation.Process()
launchctl.executableURL = URL(fileURLWithPath: "/bin/launchctl")
launchctl.arguments = ["print", target]
let stderrPipe = Pipe()
launchctl.standardOutput = FileHandle.nullDevice
launchctl.standardError = stderrPipe
try launchctl.run()
let errorData = stderrPipe.fileHandleForReading.readDataToEndOfFile()
launchctl.waitUntilExit()
return (launchctl.terminationStatus, String(decoding: errorData, as: UTF8.self))
}
static func interpretPrintStatus(_ status: Int32, target: String = "", standardError: String = "") throws -> Bool {
switch status {
case 0:
return true
case LaunchctlStatus.noSuchService:
return false
default:
var message = "command `launchctl print \(target)` failed with status \(status)"
let details = standardError.trimmingCharacters(in: .whitespacesAndNewlines)
if !details.isEmpty {
message += ", message: \(details)"
}
throw ContainerizationError(.internalError, message: message)
}
}
private static func getLaunchdSessionType() throws -> String {
@@ -0,0 +1,55 @@
//===----------------------------------------------------------------------===//
// Copyright © 2026 Apple Inc. and the container 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 Foundation
import Testing
@testable import ContainerPlugin
struct ServiceManagerTests {
@Test
func testInterpretPrintStatusSuccessMeansRegistered() throws {
#expect(try ServiceManager.interpretPrintStatus(0))
}
@Test
func testInterpretPrintStatusNoSuchServiceMeansUnregistered() throws {
#expect(try !ServiceManager.interpretPrintStatus(ServiceManager.LaunchctlStatus.noSuchService))
}
@Test
func testInterpretPrintStatusUnexpectedStatusThrows() throws {
let statuses: [Int32] = [1, 3, 64, 112]
for status in statuses {
#expect {
_ = try ServiceManager.interpretPrintStatus(
status,
target: "gui/501/com.apple.container.apiserver",
standardError: "marker"
)
} throws: { error in
"\(error)".contains("status \(status), message: marker")
}
}
}
@Test
func testIsRegisteredUnknownLabel() throws {
let domain = try ServiceManager.getDomainString()
let label = "\(domain)/com.apple.container.bogus-\(UUID().uuidString)"
#expect(try !ServiceManager.isRegistered(fullServiceLabel: label))
}
}