Index: src/OFApplication.h ================================================================== --- src/OFApplication.h +++ src/OFApplication.h @@ -135,11 +135,13 @@ #ifndef OF_WINDOWS void (*_Nullable _SIGHUPHandler)(id, SEL); void (*_Nullable _SIGUSR1Handler)(id, SEL); void (*_Nullable _SIGUSR2Handler)(id, SEL); #endif - OFSandbox *_Nullable _activeSandbox; +#ifdef OF_HAVE_SANDBOX + OFSandbox *_Nullable _activeSandbox, *_Nullable _activeExecSandbox; +#endif } #ifdef OF_HAVE_CLASS_PROPERTIES @property (class, readonly, nullable, nonatomic) OFApplication *sharedApplication; @@ -170,15 +172,24 @@ * @brief The delegate of the application. */ @property OF_NULLABLE_PROPERTY (assign, nonatomic) id delegate; +#ifdef OF_HAVE_SANDBOX /*! * @brief The sandbox currently active for this application. */ @property OF_NULLABLE_PROPERTY (readonly, nonatomic) OFSandbox *activeSandbox; +/*! + * @brief The sandbox currently active for `exec()`'d processes of this + * application. + */ +@property OF_NULLABLE_PROPERTY (readonly, nonatomic) + OFSandbox *activeExecSandbox; +#endif + /*! * @brief Returns the only OFApplication instance in the application. * * @return The only OFApplication instance in the application */ @@ -220,14 +231,31 @@ #ifdef OF_HAVE_SANDBOX /*! * @brief Activates the specified sandbox for the application. * * This is only available if `OF_HAVE_SANDBOX` is defined. + * + * @warning If you allow `exec()`, but do not call + * @ref activateSandboxForExecdProcesses, an `exec()`'d process does not have + * its permissions restricted! * * @param sandbox The sandbox to activate */ + (void)activateSandbox: (OFSandbox *)sandbox; + +/*! + * @brief Activates the specified sandbox for `exec()`'d processes of the + * application. + * + * This is only available if `OF_HAVE_SANDBOX` is defined. + * + * `unveiledPaths` on the sandbox must *not* be empty, otherwise an + * @ref OFInvalidArgumentException is raised. + * + * @param sandbox The sandbox to activate + */ ++ (void)activateSandboxForExecdProcesses: (OFSandbox *)sandbox; #endif - (instancetype)init OF_UNAVAILABLE; /*! @@ -254,14 +282,31 @@ #ifdef OF_HAVE_SANDBOX /*! * @brief Activates the specified sandbox for the application. * * This is only available if `OF_HAVE_SANDBOX` is defined. + * + * @warning If you allow `exec()`, but do not call + * @ref activateSandboxForExecdProcesses, an `exec()`'d process does not have + * its permissions restricted! * * @param sandbox The sandbox to activate */ - (void)activateSandbox: (OFSandbox *)sandbox; + +/*! + * @brief Activates the specified sandbox for `exec()`'d processes of the + * application. + * + * This is only available if `OF_HAVE_SANDBOX` is defined. + * + * `unveiledPaths` on the sandbox must *not* be empty, otherwise an + * @ref OFInvalidArgumentException is raised. + * + * @param sandbox The sandbox to activate + */ +- (void)activateSandboxForExecdProcesses: (OFSandbox *)sandbox; #endif @end #ifdef __cplusplus extern "C" { Index: src/OFApplication.m ================================================================== --- src/OFApplication.m +++ src/OFApplication.m @@ -27,24 +27,26 @@ #include #include "unistd_wrapper.h" #import "OFApplication.h" -#import "OFString.h" #import "OFArray.h" #import "OFDictionary.h" -#import "OFLocale.h" -#import "OFRunLoop.h" -#import "OFRunLoop+Private.h" -#import "OFThread.h" -#import "OFThread+Private.h" -#import "OFSandbox.h" #ifdef OF_AMIGAOS # import "OFFile.h" # import "OFFileManager.h" #endif +#import "OFLocale.h" +#import "OFPair.h" +#import "OFRunLoop+Private.h" +#import "OFRunLoop.h" +#import "OFSandbox.h" +#import "OFString.h" +#import "OFThread+Private.h" +#import "OFThread.h" +#import "OFInvalidArgumentException.h" #import "OFOutOfMemoryException.h" #import "OFOutOfRangeException.h" #import "OFSandboxActivationFailedException.h" #if defined(OF_MACOS) @@ -154,11 +156,15 @@ return 0; } @implementation OFApplication @synthesize programName = _programName, arguments = _arguments; -@synthesize environment = _environment, activeSandbox = _activeSandbox; +@synthesize environment = _environment; +#ifdef OF_HAVE_SANDBOX +@synthesize activeSandbox = _activeSandbox; +@synthesize activeExecSandbox = _activeExecSandbox; +#endif + (OFApplication *)sharedApplication { return app; } @@ -201,10 +207,15 @@ #ifdef OF_HAVE_SANDBOX + (void)activateSandbox: (OFSandbox *)sandbox { [app activateSandbox: sandbox]; } + ++ (void)activateSandboxForExecdProcesses: (OFSandbox *)sandbox +{ + [app activateSandboxForExecdProcesses: sandbox]; +} #endif - (instancetype)init { OF_INVALID_INIT_METHOD @@ -581,13 +592,25 @@ #ifdef OF_HAVE_SANDBOX - (void)activateSandbox: (OFSandbox *)sandbox { # ifdef OF_HAVE_PLEDGE void *pool = objc_autoreleasePoolPush(); + of_string_encoding_t encoding = [OFLocale encoding]; const char *promises = [[sandbox pledgeString] - cStringWithEncoding: [OFLocale encoding]]; + cStringWithEncoding: encoding]; OFSandbox *oldSandbox; + + for (of_sandbox_unveil_path_t unveiledPath in [sandbox unveiledPaths]) { + OFString *path = [unveiledPath firstObject]; + OFString *permissions = [unveiledPath secondObject]; + + if (path == nil || permissions == nil) + @throw [OFInvalidArgumentException exception]; + + unveil([path cStringWithEncoding: encoding], + [permissions cStringWithEncoding: encoding]); + } if (pledge(promises, NULL) != 0) @throw [OFSandboxActivationFailedException exceptionWithSandbox: sandbox errNo: errno]; @@ -597,7 +620,31 @@ oldSandbox = _activeSandbox; _activeSandbox = [sandbox retain]; [oldSandbox release]; # endif } + +- (void)activateSandboxForExecdProcesses: (OFSandbox *)sandbox +{ +# ifdef OF_HAVE_PLEDGE + void *pool = objc_autoreleasePoolPush(); + const char *promises = [[sandbox pledgeString] + cStringWithEncoding: [OFLocale encoding]]; + OFSandbox *oldSandbox; + + if ([[sandbox unveiledPaths] count] != 0) + @throw [OFInvalidArgumentException exception]; + + if (pledge(NULL, promises) != 0) + @throw [OFSandboxActivationFailedException + exceptionWithSandbox: sandbox + errNo: errno]; + + objc_autoreleasePoolPop(pool); + + oldSandbox = _activeExecSandbox; + _activeExecSandbox = [sandbox retain]; + [oldSandbox release]; +# endif +} #endif @end Index: src/OFSandbox.h ================================================================== --- src/OFSandbox.h +++ src/OFSandbox.h @@ -16,12 +16,22 @@ */ #import "OFObject.h" OF_ASSUME_NONNULL_BEGIN + +/*! @file */ @class OFArray OF_GENERIC(ObjectType); +@class OFMutableArray OF_GENERIC(ObjectType); +@class OFPair OF_GENERIC(FirstType, SecondType); + +/*! + * @brief An @ref OFPair for a path to unveil, with the first string being the + * path and the second the permissions. + */ +typedef OFPair OF_GENERIC(OFString *, OFString *) *of_sandbox_unveil_path_t; /*! * @class OFSandbox OFSandbox.h ObjFW/OFSandbox.h * * @brief A class which describes a sandbox for the application. @@ -54,10 +64,13 @@ unsigned int _allowsVMInfo: 1; unsigned int _allowsChangingProcessRights: 1; unsigned int _allowsPF: 1; unsigned int _allowsAudio: 1; unsigned int _allowsBPF: 1; + unsigned int _allowsUnveil: 1; + unsigned int _returnsErrors: 1; + OFMutableArray OF_GENERIC(of_sandbox_unveil_path_t) *_unveiledPaths; } /*! * @brief Allows IO operations on previously allocated file descriptors. */ @@ -196,21 +209,58 @@ /*! * @brief Allows BIOCGSTATS to collect statistics from a BPF device. */ @property (nonatomic) bool allowsBPF; +/*! + * @brief Allows unveiling more paths. + */ +@property (nonatomic) bool allowsUnveil; + +/*! + * @brief Returns errors instead of killing the process. + */ +@property (nonatomic) bool returnsErrors; + +#ifdef OF_HAVE_PLEDGE +/*! + * The string for OpenBSD's pledge() call. + * + * @warning Only available on systems with the pledge() call! + */ +@property (readonly, nonatomic) OFString *pledgeString; +#endif + +/*! + * @brief A list of unveiled paths. + */ +@property (readonly, nonatomic) + OFArray OF_GENERIC(of_sandbox_unveil_path_t) *unveiledPaths; + /*! * @brief Create a new, autorelease OFSandbox. */ + (instancetype)sandbox; -#ifdef OF_HAVE_PLEDGE -/*! - * @brief Returns the string for OpenBSD's pledge() call. - * - * @warning Only available on systems with the pledge() call! - */ -- (OFString *)pledgeString; -#endif +/*! + * @brief "Unveils" the specified path, meaning that it becomes visible from + * the sandbox with the specified permissions. + * + * @param path The path to unveil + * @param permissions The permissions for the path. The following permissions + * can be combined: + * Permission | Description + * -----------|-------------------- + * r | Make the path available for reading, like + * | @ref allowsReadingFiles + * w | Make the path available for writing, like + * | @ref allowsWritingFiles + * x | Make the path available for executing, like + * | @ref allowsExec + * c | Make the path available for creation and + * | deletion, like @ref allowsCreatingFiles + */ +- (void)unveilPath: (OFString *)path + permissions: (OFString *)permissions; @end OF_ASSUME_NONNULL_END Index: src/OFSandbox.m ================================================================== --- src/OFSandbox.m +++ src/OFSandbox.m @@ -16,18 +16,42 @@ */ #include "config.h" #import "OFSandbox.h" -#import "OFString.h" #import "OFArray.h" +#import "OFPair.h" +#import "OFString.h" @implementation OFSandbox +@synthesize unveiledPaths = _unveiledPaths; + + (instancetype)sandbox { return [[[self alloc] init] autorelease]; } + +- (instancetype)init +{ + self = [super init]; + + @try { + _unveiledPaths = [[OFMutableArray alloc] init]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_unveiledPaths release]; + + [super dealloc]; +} - (void)setAllowsStdIO: (bool)allowsStdIO { _allowsStdIO = allowsStdIO; } @@ -304,10 +328,30 @@ - (bool)allowsBPF { return _allowsBPF; } + +- (void)setAllowsUnveil: (bool)allowsUnveil +{ + _allowsUnveil = allowsUnveil; +} + +- (bool)allowsUnveil +{ + return _allowsUnveil; +} + +- (void)setReturnsErrors: (bool)returnsErrors +{ + _returnsErrors = returnsErrors; +} + +- (bool)returnsErrors +{ + return _returnsErrors; +} - (id)copy { OFSandbox *copy = [[OFSandbox alloc] init]; @@ -337,10 +381,12 @@ copy->_allowsVMInfo = _allowsVMInfo; copy->_allowsChangingProcessRights = _allowsChangingProcessRights; copy->_allowsPF = _allowsPF; copy->_allowsAudio = _allowsAudio; copy->_allowsBPF = _allowsBPF; + copy->_allowsUnveil = _allowsUnveil; + copy->_returnsErrors = _returnsErrors; return copy; } - (bool)isEqual: (id)object @@ -413,10 +459,14 @@ return false; if (sandbox->_allowsAudio != _allowsAudio) return false; if (sandbox->_allowsBPF != _allowsBPF) return false; + if (sandbox->_allowsUnveil != _allowsUnveil) + return false; + if (sandbox->_returnsErrors != _returnsErrors) + return false; return true; } - (uint32_t)hash @@ -451,10 +501,12 @@ OF_HASH_ADD(hash, _allowsVMInfo); OF_HASH_ADD(hash, _allowsChangingProcessRights); OF_HASH_ADD(hash, _allowsPF); OF_HASH_ADD(hash, _allowsAudio); OF_HASH_ADD(hash, _allowsBPF); + OF_HASH_ADD(hash, _allowsUnveil); + OF_HASH_ADD(hash, _returnsErrors); OF_HASH_FINALIZE(hash); return hash; } @@ -520,10 +572,14 @@ [pledges addObject: @"pf"]; if (_allowsAudio) [pledges addObject: @"audio"]; if (_allowsBPF) [pledges addObject: @"bpf"]; + if (_allowsUnveil) + [pledges addObject: @"unveil"]; + if (_returnsErrors) + [pledges addObject: @"error"]; ret = [pledges componentsJoinedByString: @" "]; [ret retain]; @@ -530,6 +586,22 @@ objc_autoreleasePoolPop(pool); return [ret autorelease]; } #endif + +- (void)unveilPath: (OFString *)path + permissions: (OFString *)permissions +{ + void *pool = objc_autoreleasePoolPush(); + + [_unveiledPaths addObject: [OFPair pairWithFirstObject: path + secondObject: permissions]]; + + objc_autoreleasePoolPop(pool); +} + +- (OFArray OF_GENERIC(of_sandbox_unveil_path_t) *)unveiledPaths +{ + return [[_unveiledPaths copy] autorelease]; +} @end