Browse Source

process: change current directory to socket destination

When we use AF_UNIX, the path has a maximum length of 104 characters. In the
past, we dependend on buggy behaviour that truncates the path length and both
QEMU and SPICE used the truncated path as the destination. This can result in
collisions between different VMs as well as other issues.

Now, we change the current working directory to where the AF_UNIX socket file
will reside and use relative addresses for both processes.
osy 2 years ago
parent
commit
bd78994000

+ 11 - 7
Configuration/UTMQemuConfiguration+Arguments.swift

@@ -28,10 +28,10 @@ import Virtualization // for getting network interfaces
         QEMUArgumentFragment(final: string)
     }
     
-    /// Return the socket file for communicating with SPICE
-    var spiceSocketURL: URL {
+    /// Shared between helper and main process to store Unix sockets
+    var socketURL: URL {
         #if os(iOS)
-        let parentURL = FileManager.default.temporaryDirectory
+        return FileManager.default.temporaryDirectory
         #else
         let appGroup = Bundle.main.infoDictionary?["AppGroupIdentifier"] as? String
         let helper = Bundle.main.infoDictionary?["HelperIdentifier"] as? String
@@ -44,11 +44,16 @@ import Virtualization // for getting network interfaces
         parentURL.appendPathComponent("tmp")
         if let appGroup = appGroup, !appGroup.hasPrefix("invalid.") {
             if let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) {
-                parentURL = containerURL
+                return containerURL
             }
         }
+        return parentURL
         #endif
-        return parentURL.appendingPathComponent("\(information.uuid.uuidString).spice")
+    }
+    
+    /// Return the socket file for communicating with SPICE
+    var spiceSocketURL: URL {
+        socketURL.appendingPathComponent(information.uuid.uuidString).appendingPathExtension("spice")
     }
     
     /// Combined generated and user specified arguments.
@@ -100,8 +105,7 @@ import Virtualization // for getting network interfaces
     @QEMUArgumentBuilder private var spiceArguments: [QEMUArgument] {
         f("-spice")
         "unix=on"
-        "addr="
-        spiceSocketURL
+        "addr=\(spiceSocketURL.lastPathComponent)"
         "disable-ticketing=on"
         "image-compression=off"
         "playback-compression=off"

+ 4 - 0
QEMUHelper/QEMUHelper.m

@@ -28,6 +28,7 @@
 @implementation QEMUHelper
 
 @synthesize environment;
+@synthesize currentDirectoryPath;
 
 - (instancetype)init {
     if (self = [super init]) {
@@ -128,6 +129,9 @@
         [environment addEntriesFromDictionary:self.environment];
     }
     task.environment = environment;
+    if (self.currentDirectoryPath) {
+        task.currentDirectoryURL = [NSURL fileURLWithPath:self.currentDirectoryPath];
+    }
     task.qualityOfService = NSQualityOfServiceUserInitiated;
     task.terminationHandler = ^(NSTask *task) {
         _self.childTask = nil;

+ 1 - 0
QEMUHelper/QEMUHelperProtocol.h

@@ -22,6 +22,7 @@ NS_ASSUME_NONNULL_BEGIN
 @protocol QEMUHelperProtocol
 
 @property (nonatomic, nullable) NSDictionary<NSString *, NSString *> *environment;
+@property (nonatomic, nullable) NSString *currentDirectoryPath;
 
 - (void)accessDataWithBookmark:(NSData *)bookmark securityScoped:(BOOL)securityScoped completion:(void(^)(BOOL, NSData * _Nullable, NSString * _Nullable))completion;
 - (void)stopAccessingPath:(nullable NSString *)path;

+ 1 - 0
Services/UTMProcess.h

@@ -33,6 +33,7 @@ typedef int (*UTMProcessThreadEntry)(UTMProcess *self, int argc, const char * _N
 @property (nonatomic) UTMProcessThreadEntry entry;
 @property (nonatomic, nullable) NSPipe *standardOutput;
 @property (nonatomic, nullable) NSPipe *standardError;
+@property (nonatomic, nullable) NSURL *currentDirectoryUrl;
 
 - (instancetype)init;
 - (instancetype)initWithArguments:(NSArray<NSString *> *)arguments NS_DESIGNATED_INITIALIZER;

+ 6 - 0
Services/UTMProcess.m

@@ -57,6 +57,11 @@ static void *startProcess(void *args) {
     envp[envc] = NULL;
     setenv("TMPDIR", NSFileManager.defaultManager.temporaryDirectory.path.UTF8String, 1);
     
+    const char *currentDirectoryPath = self.currentDirectoryUrl.path.UTF8String;
+    if (currentDirectoryPath) {
+        chdir(currentDirectoryPath);
+    }
+    
     int argc = (int)processArgv.count + 1;
     const char *argv[argc];
     argv[0] = [self.processName cStringUsingEncoding:NSUTF8StringEncoding];
@@ -228,6 +233,7 @@ static int defaultEntry(UTMProcess *self, int argc, const char *argv[], const ch
     NSFileHandle *standardOutput = self.standardOutput.fileHandleForWriting;
     NSFileHandle *standardError = self.standardError.fileHandleForWriting;
     [_connection.remoteObjectProxy setEnvironment:self.environment];
+    [_connection.remoteObjectProxy setCurrentDirectoryPath:self.currentDirectoryUrl.path];
     [[_connection remoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) {
         if (error.domain == NSCocoaErrorDomain && error.code == NSXPCConnectionInvalid) {
             // inhibit this error since we always see it on quit

+ 1 - 0
Services/UTMQemuVirtualMachine.swift

@@ -248,6 +248,7 @@ extension UTMQemuVirtualMachine {
         
         let system = await UTMQemuSystem(arguments: arguments, architecture: config.system.architecture.rawValue)
         system.resources = resources
+        system.currentDirectoryUrl = await config.socketURL
         system.remoteBookmarks = remoteBookmarks as NSDictionary
         system.rendererBackend = rendererBackend
         try Task.checkCancellation()

+ 10 - 1
Services/UTMSpiceIO.m

@@ -66,7 +66,8 @@ NSString *const kUTMErrorDomain = @"com.utmapp.utm";
 
 - (void)initializeSpiceIfNeeded {
     if (!self.spiceConnection) {
-        self.spiceConnection = [[CSConnection alloc] initWithUnixSocketFile:self.socketUrl];
+        NSURL *relativeSocketFile = [NSURL fileURLWithPath:self.socketUrl.lastPathComponent];
+        self.spiceConnection = [[CSConnection alloc] initWithUnixSocketFile:relativeSocketFile];
         self.spiceConnection.delegate = self;
         self.spiceConnection.audioEnabled = (self.options & UTMSpiceIOOptionsHasAudio) == UTMSpiceIOOptionsHasAudio;
         self.spiceConnection.session.shareClipboard = (self.options & UTMSpiceIOOptionsHasClipboardSharing) == UTMSpiceIOOptionsHasClipboardSharing;
@@ -85,6 +86,14 @@ NSString *const kUTMErrorDomain = @"com.utmapp.utm";
 #endif
     // do not need to encode/decode audio locally
     g_setenv("SPICE_DISABLE_OPUS", "1", TRUE);
+    // need to chdir to workaround AF_UNIX sun_len limitations
+    NSString *curdir = self.socketUrl.URLByDeletingLastPathComponent.path;
+    if (!curdir || ![NSFileManager.defaultManager changeCurrentDirectoryPath:curdir]) {
+        if (error) {
+            *error = [NSError errorWithDomain:kUTMErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: NSLocalizedString(@"Failed to change current directory.", "UTMSpiceIO")}];
+        }
+        return NO;
+    }
     if (![self.spice spiceStart]) {
         if (error) {
             *error = [NSError errorWithDomain:kUTMErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: NSLocalizedString(@"Failed to start SPICE client.", "UTMSpiceIO")}];