trustdFileLocations.m   [plain text]

/*
* Copyright (c) 2020 Apple Inc. All Rights Reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/

#include <Foundation/Foundation.h>
#import <Foundation/NSXPCConnection_Private.h>
#include <sys/stat.h>
#include <unistd.h>

#include <utilities/SecFileLocations.h>
#include <utilities/debugging.h>
#include <Security/SecItemInternal.h>

#include "OTATrustUtilities.h"
#include "trustdFileLocations.h"

#if TARGET_OS_OSX
#include <membership.h>
#endif

#if !TARGET_OS_SIMULATOR && (TARGET_OS_IPHONE || TARGET_CPU_ARM64)
#include <System/sys/content_protection.h>
#endif

#define ROOT_ACCOUNT 0

bool SecOTAPKIIsSystemTrustd() {
static bool result = false;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
#ifdef NO_SERVER
// Test app running as trustd
result = true;
#else // !NO_SERVER
if (getuid() == TRUSTD_ROLE_ACCOUNT ||
(getuid() == ROOT_ACCOUNT && gTrustd)) { // Test app running as trustd
result = true;
}
#endif // !NO_SERVER
});
return result;
}

/*
* trustd's data vault hierarchy:
* * /var/protected/trustd - requires an entitlement to write but is available to read by all processes
* used for non-privacy sensitive system data (e.g. Valid DB, pinning DB, MobileAsset files)
* * /var/protected/trustd/private - requires an entitlement to read and write, trustds running as every user can rwx directory
* used for sensitive system data (e.g. administrative configuration and admin trust store)
* * /var/protected/trustd/private/<UUID> - inherits the entitlement requirement, but uses unix permissions to restrict rwx to
* the trustd running as the user corresponding to the uuid. Used for sensitive user
* data (e.g. OCSP and CA issuer caches, user trust store)
*/

CFURLRef SecCopyURLForFileInProtectedTrustdDirectory(CFStringRef fileName)
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
WithPathInProtectedDirectory(CFSTR("trustd"), ^(const char *path) {
mode_t permissions = 0777; // Non-system trustd's create directory with expansive permissions
int ret = mkpath_np(path, permissions);
if (!(ret == 0 || ret == EEXIST)) {
secerror("could not create path: %s (%s)", path, strerror(ret));
}
if (SecOTAPKIIsSystemTrustd()) { // System trustd fixes them up since only it should be writing
permissions = 0755;
uid_t currentUid = getuid();
chown(path, currentUid, currentUid);
chmod(path, permissions);
}
});
});
NSString *path = @"trustd/";
if (fileName) {
path = [NSString stringWithFormat:@"trustd/%@", fileName];
}
return SecCopyURLForFileInProtectedDirectory((__bridge CFStringRef)path);
}

CFURLRef SecCopyURLForFileInPrivateTrustdDirectory(CFStringRef fileName)
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
WithPathInProtectedTrustdDirectory(CFSTR("private"), ^(const char *path) {
mode_t permissions = 0777;
int ret = mkpath_np(path, permissions);
if (!(ret == 0 || ret == EEXIST)) {
secerror("could not create path: %s (%s)", path, strerror(ret));
}
chmod(path, permissions);
});
});
NSString *path = @"private/";
if (fileName) {
path = [NSString stringWithFormat:@"private/%@", fileName];
}
return SecCopyURLForFileInProtectedTrustdDirectory((__bridge CFStringRef)path);
}

CFURLRef SecCopyURLForFileInPrivateUserTrustdDirectory(CFStringRef fileName)
{
#if TARGET_OS_OSX
uid_t euid = geteuid();
uuid_t currentUserUuid;
int ret = mbr_uid_to_uuid(euid, currentUserUuid);
if (ret != 0) {
secerror("failed to get UUID for user(%d) - %d", euid, ret);
return SecCopyURLForFileInPrivateTrustdDirectory(fileName);
}
NSUUID *userUuid = [[NSUUID alloc] initWithUUIDBytes:currentUserUuid];
NSString *directory = [NSString stringWithFormat:@"/%@",[userUuid UUIDString]];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
WithPathInPrivateTrustdDirectory((__bridge CFStringRef)directory, ^(const char *path) {
mode_t permissions = 0700;
int mkpath_ret = mkpath_np(path, permissions);
if (!(mkpath_ret == 0 || mkpath_ret == EEXIST)) {
secerror("could not create path: %s (%s)", path, strerror(ret));
}
chmod(path, permissions);
});
});
NSString *path = directory;
if (fileName) {
path = [NSString stringWithFormat:@"%@/%@", directory, fileName];
}
return SecCopyURLForFileInPrivateTrustdDirectory((__bridge CFStringRef)path);
#else
return SecCopyURLForFileInPrivateTrustdDirectory(fileName);
#endif
}

CFURLRef SecCopyURLForFileInRevocationInfoDirectory(CFStringRef fileName)
{
return SecCopyURLForFileInProtectedTrustdDirectory(fileName);
}

void WithPathInRevocationInfoDirectory(CFStringRef fileName, void(^operation)(const char *utf8String))
{
WithPathInDirectory(SecCopyURLForFileInRevocationInfoDirectory(fileName), operation);
}

void WithPathInProtectedTrustdDirectory(CFStringRef fileName, void(^operation)(const char *utf8String))
{
WithPathInDirectory(SecCopyURLForFileInProtectedTrustdDirectory(fileName), operation);
}

void WithPathInPrivateTrustdDirectory(CFStringRef fileName, void(^operation)(const char *utf8String))
{
WithPathInDirectory(SecCopyURLForFileInPrivateTrustdDirectory(fileName), operation);
}

void WithPathInPrivateUserTrustdDirectory(CFStringRef fileName, void(^operation)(const char *utf8String))
{
WithPathInDirectory(SecCopyURLForFileInPrivateUserTrustdDirectory(fileName), operation);
}

static BOOL needToFixFilePermissions(void) {
__block BOOL result = NO;
#if TARGET_OS_SIMULATOR
// Simulators don't upgrade, so no need to fix file permissions
#elif TARGET_OS_IPHONE
/* Did trust store already migrate */
WithPathInPrivateTrustdDirectory(CFSTR("TrustStore.sqlite3"), ^(const char *utf8String) {
struct stat sb;
int ret = stat(utf8String, &sb);
if (ret != 0) {
secinfo("helper", "failed to stat TrustStore: %s", strerror(errno));
result = YES;
}
});
/* Can we write to the analytics directory */
WithPathInKeychainDirectory(CFSTR("Analytics"), ^(const char *utf8String) {
struct stat sb;
int ret = stat(utf8String, &sb);
if (ret != 0) {
// Check errno. Missing directory or file errors do not require any fixes
if (errno != ENOTDIR && errno != ENOENT) {
secinfo("helper", "failed to stat Analytics dir: %s", strerror(errno));
result = YES;
}
} else {
// Check that all users have rwx for Analytics dir
if ((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) != (S_IRWXU | S_IRWXG | S_IRWXO)) {
secinfo("helper", "wrong permissions on Analytics dir: %d", sb.st_mode);
result = YES;
}
}
});
#else
/* Can we read/write Valid file? */
WithPathInRevocationInfoDirectory(CFSTR("valid.sqlite3"), ^(const char *utf8String) {
struct stat sb;
int ret = stat(utf8String, &sb);
if (ret != 0) {
// Check errno. Missing directory or file errors do not require any fixes
if (errno != ENOTDIR && errno != ENOENT) {
secinfo("helper", "failed to stat valid db: %s", strerror(errno));
result = YES;
}
} else {
// Successful call. Check if the file owner has been changed
if (sb.st_uid != TRUSTD_ROLE_ACCOUNT) {
secinfo("helper", "wrong owner for valid db");
result = YES;
}
}
});
#endif
return result;
}

static NSXPCConnection* getConnection()
{
#if TARGET_OS_OSX
NSString *xpcServiceName = @TrustdFileHelperXPCServiceName;
#else
NSString *xpcServiceName = @"com.apple.securityuploadd";
#endif
NSXPCConnection* connection = [[NSXPCConnection alloc] initWithMachServiceName:xpcServiceName options:0];
connection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(TrustdFileHelper_protocol)];
[connection resume];
return connection;
}

void FixTrustdFilePermissions(void)
{
#if TARGET_OS_OSX
const NSString *processName = @"trustdFileHelper";
#else
const NSString *processName = @"securityuploadd";
#endif
@autoreleasepool {
if (!needToFixFilePermissions()) {
secinfo("helper", "trustd file permissions already fixed. skipping trustdFileHelper call.");
return;
}
@try {
NSXPCConnection *connection = getConnection();
if (!connection) {
secerror("failed to ceate %{public}@ connection", processName);
} else {
id<TrustdFileHelper_protocol> protocol = [connection synchronousRemoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) {
secerror("failed to talk to %{public}@: %@", processName, error);
}];
if (protocol) {
secdebug("ipc", "asking the file helper to fix our files");
[protocol fixFiles:^(BOOL result, NSError *error) {
if (!result) {
secerror("%{public}@ failed to fix our files: %@", processName, error);
} else {
secdebug("ipc", "file helper successfully completed fixes");
}
}];
}
}
}
@catch(id anException) {
secerror("failed to fix files; caught exception: %@", anException);
}
}
}

bool TrustdChangeFileProtectionToClassD(const char *filename, CFErrorRef *error) {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
bool result = true;
int file_fd = open(filename, O_RDONLY);
if (file_fd) {
int retval = fcntl(file_fd, F_SETPROTECTIONCLASS, PROTECTION_CLASS_D);
if (retval < 0) {
NSError *localError = [NSError errorWithDomain:NSPOSIXErrorDomain
code:errno
userInfo:@{NSLocalizedDescriptionKey : [NSString localizedStringWithFormat:@"failed to change protection class of %s: %s", filename, strerror(errno)]}];
if (error && !*error) {
*error = (CFErrorRef)CFBridgingRetain(localError);
}
result = false;
}
close(file_fd);
} else {
NSError *localError = [NSError errorWithDomain:NSPOSIXErrorDomain
code:errno
userInfo:@{NSLocalizedDescriptionKey : [NSString localizedStringWithFormat:@"failed to open file for protection class change %s: %s", filename, strerror(errno)]}];
if (error && !*error) {
*error = (CFErrorRef)CFBridgingRetain(localError);
}
result = false;
}
return result;
#else // !TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
return true;
#endif
}

@implementation NSDictionary (trustdAdditions)
- (BOOL)writeToClassDURL:(NSURL *)url permissions:(mode_t)permissions error:(NSError **)error {
if ([self writeToURL:url error:error]) {
CFErrorRef localError = NULL;
if (!TrustdChangeFileProtectionToClassD([url fileSystemRepresentation], &localError)) {
if (error) {
*error = CFBridgingRelease(localError);
} else {
CFReleaseNull(error);
}
return NO;
}
int ret = chmod([url fileSystemRepresentation], permissions);
if (!(ret == 0)) {
int localErrno = errno;
secerror("failed to change permissions of %s: %s", [url fileSystemRepresentation], strerror(localErrno));
if (error) {
*error = [NSError errorWithDomain:NSPOSIXErrorDomain code:localErrno
userInfo:@{NSLocalizedDescriptionKey : [NSString localizedStringWithFormat:@"failed to change permissions of %s: %s", [url fileSystemRepresentation], strerror(localErrno)]}];
}
return NO;
}
return YES;
}
return NO;
}
@end