Mac Sandbox: testing whether a file is accessible - macos

Does anybody know whether there's a way of finding out whether a particular file system location is accessible under the sandbox?
I want to test whether a particular file is accessible under the normal Powerbox rules; that is has already been added to the power box using the open/ save dialog, etc.
Can I do this before triggering a sandbox exception?
Can I catch a sandbox exception?
Best regards,
Frank

You can use the OS access() system call for a quick and simple test, from man access:
#include <unistd.h>
int access(const char *path, int amode);
The access() function checks the accessibility of the file named by path
for the access permissions indicated by amode. The value of amode is the
bitwise inclusive OR of the access permissions to be checked (R_OK for
read permission, W_OK for write permission and X_OK for execute/search
permission) or the existence test, F_OK. All components of the pathname
path are checked for access permissions (including F_OK).
If path cannot be found or if any of the desired access modes would not
be granted, then a -1 value is returned and the global integer variable
errno is set to indicate the error. Otherwise, a 0 value is returned.
You could pretty this up for Objective-C using something like:
typedef enum
{
ReadAccess = R_OK,
WriteAccess = W_OK,
ExecuteAccess = X_OK,
PathExists = F_OK
} AccessKind;
BOOL isPathAccessible(NSString *path, AccessKind mode)
{
return access([path UTF8String], mode) == 0;
}

A few things. Always use fileSystemRepresentation when you need a path string. Also, R_OK is adequate if you just want to know if there is a hole in the sandbox for the specified path.
-(BOOL)isAccessibleFromSandbox:(NSString*)path
{
return( access( path.fileSystemRepresentation, R_OK) == 0 );
}

Related

IShellLink - how to get the original target path

I created a shortcut in a Windows PC with a target path of:
C:\Users\b\Desktop\New Text Document.txt
Then I copied the shortcut to another PC with a different user name, and I want to retrieve the original target path.
If you open the shortcut file with a text editor, you can see the original path is preserved, so the goal is definitely possible.
The following code does not work, despite the presence of SLGP_RAWPATH. It outputs:
C:\Users\a\Desktop\New Text Document.txt
It is changing the user folder name to the one associated with the running program.
I understand that the problem is not about environment variables, because no environment variable name can be seen in the file. But I can't find any documentation about this auto-relocation behavior.
IShellLinkW*lnk;
if (CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW, (LPVOID*)&lnk) == 0){
IPersistFile* file;
if (lnk->QueryInterface(IID_IPersistFile, (void**)&file) == 0){
if (file->Load(L"shortcut", 0) == 0){
wchar_t path[MAX_PATH];
if (lnk->GetPath(path, _countof(path), 0, SLGP_RAWPATH) == 0){
_putws(path);
}
IShellLinkDataList* sdl;
if (lnk->QueryInterface(IID_IShellLinkDataList, (void**)&sdl) == 0){
EXP_SZ_LINK* lnkData;
if (sdl->CopyDataBlock(EXP_SZ_LINK_SIG, (void**)&lnkData) == 0){
_putws(lnkData->swzTarget);
LocalFree(lnkData);
}
sdl->Release();
}
}
file->Release();
}
lnk->Release();
}
The Windows Shell Link class implements a property store, so you can get access to this with code like this (with ATL smart pointers):
int main()
{
// note: error checking omitted!
CoInitialize(NULL);
{
CComPtr<IShellLink> link;
link.CoCreateInstance(CLSID_ShellLink);
CComPtr<IPersistFile> file;
link->QueryInterface(&file);
file->Load(L"shortcut", STGM_READ);
// get the property store
CComPtr<IPropertyStore> ps;
link->QueryInterface(&ps);
// dump all properties
DWORD count = 0;
ps->GetCount(&count);
for (DWORD i = 0; i < count; i++)
{
PROPERTYKEY pk;
ps->GetAt(i, &pk);
// get property's canonical name from pk
CComHeapPtr<wchar_t> name;
PSGetNameFromPropertyKey(pk, &name);
PROPVARIANT pv;
PropVariantInit(&pv);
ps->GetValue(pk, &pv);
// convert PropVariants to a string to be able to display
CComHeapPtr<wchar_t> valueAsString;
PropVariantToStringAlloc(pv, &valueAsString); // propvarutil.h
wprintf(L"%s: %s\n", name, valueAsString);
PropVariantClear(&pv);
}
}
CoUninitialize();
return 0;
}
It will output this:
System.ItemNameDisplay: New Text Document.txt
System.DateCreated: 2021/06/03:14:45:30.000
System.Size: 0
System.ItemTypeText: Text Document
System.DateModified: 2021/06/03:14:45:29.777
System.ParsingPath: C:\Users\b\Desktop\New Text Document.txt
System.VolumeId: {E506CEB2-0000-0000-0000-300300000000}
System.ItemFolderPathDisplay: C:\Users\b\Desktop
So, you're looking for System.ParsingPath, which you can get directly like this:
...
ps->GetValue(PKEY_ParsingPath, &pv); // propkey.h
...
Your shortcut is a .lnk file, just without the .lnk file extension present. According to Microsoft's latest "Shell Link (.LNK) Binary File Format" documentation, your shortcut appears to be configured as a relative file target. The relative name is just New Text Document.txt. I didn't dig into the file too much, but I'm guessing that it is relative to the system's Desktop folder, so it will take on whatever the actual Desktop folder of the current PC is. Which would explain why querying the target changes the relative root from C:\Users\b\Desktop to C:\Users\a\Desktop when you change PCs.
As for being able to query the original target C:\Users\b\Desktop\New Text Document.txt, that I don't know. It is also present in the file, so in theory there should be a way to query it, but I don't know which field it is in, without taking the time to fully decode this file. You should try writing your own decoder, using the above documentation.

Enabling Closed-Display Mode w/o Meeting Apple's Requirements

EDIT:
I have heavily edited this question after making some significant new discoveries and the question not having any answers yet.
Historically/AFAIK, keeping your Mac awake while in closed-display mode and not meeting Apple's requirements, has only been possible with a kernel extension (kext), or a command run as root. Recently however, I have discovered that there must be another way. I could really use some help figuring out how to get this working for use in a (100% free, no IAP) sandboxed Mac App Store (MAS) compatible app.
I have confirmed that some other MAS apps are able to do this, and it looks like they might be writing YES to a key named clamshellSleepDisabled. Or perhaps there's some other trickery involved that causes the key value to be set to YES? I found the function in IOPMrootDomain.cpp:
void IOPMrootDomain::setDisableClamShellSleep( bool val )
{
if (gIOPMWorkLoop->inGate() == false) {
gIOPMWorkLoop->runAction(
OSMemberFunctionCast(IOWorkLoop::Action, this, &IOPMrootDomain::setDisableClamShellSleep),
(OSObject *)this,
(void *)val);
return;
}
else {
DLOG("setDisableClamShellSleep(%x)\n", (uint32_t) val);
if ( clamshellSleepDisabled != val )
{
clamshellSleepDisabled = val;
// If clamshellSleepDisabled is reset to 0, reevaluate if
// system need to go to sleep due to clamshell state
if ( !clamshellSleepDisabled && clamshellClosed)
handlePowerNotification(kLocalEvalClamshellCommand);
}
}
}
I'd like to give this a try and see if that's all it takes, but I don't really have any idea about how to go about calling this function. It's certainly not a part of the IOPMrootDomain documentation, and I can't seem to find any helpful example code for functions that are in the IOPMrootDomain documentation, such as setAggressiveness or setPMAssertionLevel. Here's some evidence of what's going on behind the scenes according to Console:
I've had a tiny bit of experience working with IOMProotDomain via adapting some of ControlPlane's source for another project, but I'm at a loss for how to get started on this. Any help would be greatly appreciated. Thank you!
EDIT:
With #pmdj's contribution/answer, this has been solved!
Full example project:
https://github.com/x74353/CDMManager
This ended up being surprisingly simple/straightforward:
1. Import header:
#import <IOKit/pwr_mgt/IOPMLib.h>
2. Add this function in your implementation file:
IOReturn RootDomain_SetDisableClamShellSleep (io_connect_t root_domain_connection, bool disable)
{
uint32_t num_outputs = 0;
uint32_t input_count = 1;
uint64_t input[input_count];
input[0] = (uint64_t) { disable ? 1 : 0 };
return IOConnectCallScalarMethod(root_domain_connection, kPMSetClamshellSleepState, input, input_count, NULL, &num_outputs);
}
3. Use the following to call the above function from somewhere else in your implementation:
io_connect_t connection = IO_OBJECT_NULL;
io_service_t pmRootDomain = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPMrootDomain"));
IOServiceOpen (pmRootDomain, current_task(), 0, &connection);
// 'enable' is a bool you should assign a YES or NO value to prior to making this call
RootDomain_SetDisableClamShellSleep(connection, enable);
IOServiceClose(connection);
I have no personal experience with the PM root domain, but I do have extensive experience with IOKit, so here goes:
You want IOPMrootDomain::setDisableClamShellSleep() to be called.
A code search for sites calling setDisableClamShellSleep() quickly reveals a location in RootDomainUserClient::externalMethod(), in the file iokit/Kernel/RootDomainUserClient.cpp. This is certainly promising, as externalMethod() is what gets called in response to user space programs calling the IOConnectCall*() family of functions.
Let's dig in:
IOReturn RootDomainUserClient::externalMethod(
uint32_t selector,
IOExternalMethodArguments * arguments,
IOExternalMethodDispatch * dispatch __unused,
OSObject * target __unused,
void * reference __unused )
{
IOReturn ret = kIOReturnBadArgument;
switch (selector)
{
…
…
…
case kPMSetClamshellSleepState:
fOwner->setDisableClamShellSleep(arguments->scalarInput[0] ? true : false);
ret = kIOReturnSuccess;
break;
…
So, to invoke setDisableClamShellSleep() you'll need to:
Open a user client connection to IOPMrootDomain. This looks straightforward, because:
Upon inspection, IOPMrootDomain has an IOUserClientClass property of RootDomainUserClient, so IOServiceOpen() from user space will by default create an RootDomainUserClient instance.
IOPMrootDomain does not override the newUserClient member function, so there are no access controls there.
RootDomainUserClient::initWithTask() does not appear to place any restrictions (e.g. root user, code signing) on the connecting user space process.
So it should simply be a case of running this code in your program:
io_connect_t connection = IO_OBJECT_NULL;
IOReturn ret = IOServiceOpen(
root_domain_service,
current_task(),
0, // user client type, ignored
&connection);
Call the appropriate external method.
From the code excerpt earlier on, we know that the selector must be kPMSetClamshellSleepState.
arguments->scalarInput[0] being zero will call setDisableClamShellSleep(false), while a nonzero value will call setDisableClamShellSleep(true).
This amounts to:
IOReturn RootDomain_SetDisableClamShellSleep(io_connect_t root_domain_connection, bool disable)
{
uint32_t num_outputs = 0;
uint64_t inputs[] = { disable ? 1 : 0 };
return IOConnectCallScalarMethod(
root_domain_connection, kPMSetClamshellSleepState,
&inputs, 1, // 1 = length of array 'inputs'
NULL, &num_outputs);
}
When you're done with your io_connect_t handle, don't forget to IOServiceClose() it.
This should let you toggle clamshell sleep on or off. Note that there does not appear to be any provision for automatically resetting the value to its original state, so if your program crashes or exits without cleaning up after itself, whatever state was last set will remain. This might not be great from a user experience perspective, so perhaps try to defend against it somehow, for example in a crash handler.

Workaround for a certain IOServiceOpen() call requiring root privileges

Background
It is possible to perform a software-controlled disconnection of the power adapter of a Mac laptop by creating an DisableInflow power management assertion.
Code from this answer to an SO question can be used to create said assertion. The following is a working example that creates this assertion until the process is killed:
#include <IOKit/pwr_mgt/IOPMLib.h>
#include <unistd.h>
int main()
{
IOPMAssertionID neverSleep = 0;
IOPMAssertionCreateWithName(kIOPMAssertionTypeDisableInflow,
kIOPMAssertionLevelOn,
CFSTR("disable inflow"),
&neverSleep);
while (1)
{
sleep(1);
}
}
This runs successfully and the power adapter is disconnected by software while the process is running.
What's interesting, though, is that I was able to run this code as a regular user, without root privileges, which wasn't supposed to happen. For instance, note the comment in this file from Apple's open source repositories:
// Disables AC Power Inflow (requires root to initiate)
#define kIOPMAssertionTypeDisableInflow CFSTR("DisableInflow")
#define kIOPMInflowDisableAssertion kIOPMAssertionTypeDisableInflow
I found some code which apparently performs the actual communication with the charger; it can be found here. The following functions, from this file, appears to be of particular interest:
IOReturn
AppleSmartBatteryManagerUserClient::externalMethod(
uint32_t selector,
IOExternalMethodArguments * arguments,
IOExternalMethodDispatch * dispatch __unused,
OSObject * target __unused,
void * reference __unused )
{
if (selector >= kNumBattMethods) {
// Invalid selector
return kIOReturnBadArgument;
}
switch (selector)
{
case kSBInflowDisable:
// 1 scalar in, 1 scalar out
return this->secureInflowDisable((int)arguments->scalarInput[0],
(int *)&arguments->scalarOutput[0]);
break;
// ...
}
// ...
}
IOReturn AppleSmartBatteryManagerUserClient::secureInflowDisable(
int level,
int *return_code)
{
int admin_priv = 0;
IOReturn ret = kIOReturnNotPrivileged;
if( !(level == 0 || level == 1))
{
*return_code = kIOReturnBadArgument;
return kIOReturnSuccess;
}
ret = clientHasPrivilege(fOwningTask, kIOClientPrivilegeAdministrator);
admin_priv = (kIOReturnSuccess == ret);
if(admin_priv && fOwner) {
*return_code = fOwner->disableInflow( level );
return kIOReturnSuccess;
} else {
*return_code = kIOReturnNotPrivileged;
return kIOReturnSuccess;
}
}
Note how, in secureInflowDisable(), root privileges are checked for prior to running the code. Note also this initialization code in the same file, again requiring root privileges, as explicitly pointed out in the comments:
bool AppleSmartBatteryManagerUserClient::initWithTask(task_t owningTask,
void *security_id, UInt32 type, OSDictionary * properties)
{
uint32_t _pid;
/* 1. Only root processes may open a SmartBatteryManagerUserClient.
* 2. Attempts to create exclusive UserClients will fail if an
* exclusive user client is attached.
* 3. Non-exclusive clients will not be able to perform transactions
* while an exclusive client is attached.
* 3a. Only battery firmware updaters should bother being exclusive.
*/
if ( kIOReturnSuccess !=
clientHasPrivilege(owningTask, kIOClientPrivilegeAdministrator))
{
return false;
}
// ...
}
Starting from the code from the same SO question above (the question itself, not the answer), for the sendSmartBatteryCommand() function, I wrote some code that calls the function passing kSBInflowDisable as the selector (the variable which in the code).
Unlike the code using assertions, this one only works as root. If running as a regular user, IOServiceOpen() returns, weirdly enough, kIOReturnBadArgument (not kIOReturnNotPrivileged, as I would have expected). Perhaps this has to do with the initWithTask() method above.
The question
I need to perform a call with a different selector to this same Smart Battery Manager kext. Even so, I can't even get to the IOConnectCallMethod() since IOServiceOpen() fails, presumably because the initWithTask() method prevents any non-root users from opening the service.
The question, therefore, is this: how is IOPMAssertionCreateWithName() capable of creating a DisableInflow assertion without root privileges?
The only possibility I can think of is if there's a root-owned process to which requests are forwarded, and which performs the actual work of calling IOServiceOpen() and later IOConnectCallMethod() as root.
However, I'm hoping there's a different way of calling the Smart Battery Manager kext which doesn't require root (one that doesn't involve the IOServiceOpen() call.) Using IOPMAssertionCreateWithName() itself is not possible in my application, since I need to call a different selector within that kext, not the one that disables inflow.
It's also possible this is in fact a security vulnerability, which Apple will now fix in a future release as soon as it is alerted to this question. That would be too bad, but understandable.
Although running as root is a possibility in macOS, it's obviously desirable to avoid privilege elevation unless absolutely necessary. Also, in the future I'd like to run the same code under iOS, where it's impossible to run anything as root, in my understanding (note this is an app I'm developing for my own personal use; I understand linking to IOKit wipes out any chance of getting the app published in the App Store).

Ignoring exclusively locked files in SHFileOperation

I am using Windows 7 Professional and I am using SHFileOperation() to recursive copy one folder contents to another. But there is a locked file (opened exclusively by an application); I need to skip it, but SHFileOperation() returns 0x20 when tries to copy this file.
How can I skip this file during the file copy operation?
UPDATE: this is the code:
//
// CopyDirectory()
// рекурсивное копирование содержимого одной директории в другую средствами Windows
// lpszSource - исходная папка
// lpszDestination - папка назначения
//
BOOL CopyDirectory( LPSTR lpszSource, LPSTR lpszDestination )
{
LPSTR lpszNewSource = NULL;
// структура операции с файлами
SHFILEOPSTRUCT fileOP = { 0 };
// выделим память под новый путь
lpszNewSource = (LPSTR)calloc(strlen(lpszSource) + 50, 1);
// запишем новый путь с маской
wsprintf(lpszNewSource, "%s\\*", lpszSource);
// запишем параметры операции копирования
fileOP.wFunc = FO_COPY;
fileOP.pTo = lpszDestination;
fileOP.pFrom = lpszSource;
fileOP.fFlags = FOF_SILENT | FOF_NOCONFIRMMKDIR | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_NO_UI;
// выполняем операцию
INT retVal = SHFileOperation( &fileOP );
// освободим память
FREE_NULL(lpszNewSource);
DebugPrint(DEBUG_INFO, "retVal = %d\n", retVal);
// возвращаем результат копирования
return retVal == 0;
}
The SHFileOperation() documentation says:
Return value
Type: int
Returns zero if successful; otherwise nonzero. Applications normally should simply check for zero or nonzero.
It is good practice to examine the value of the fAnyOperationsAborted member of the SHFILEOPSTRUCT. SHFileOperation can return 0 for success if the user cancels the operation. If you do not check fAnyOperationsAborted as well as the return value, you cannot know that the function accomplished the full task you asked of it and you might proceed under incorrect assumptions.
Do not use GetLastError with the return values of this function.
To examine the nonzero values for troubleshooting purposes, they largely map to those defined in Winerror.h. However, several of its possible return values are based on pre-Win32 error codes, which in some cases overlap the later Winerror.h values without matching their meaning. Those particular values are detailed here, and for these specific values only these meanings should be accepted over the Winerror.h codes.
In your case, 0x20 is not one of the pre-Win32 error codes, so it maps to a standard Win32 error code, specifically ERROR_SHARING_VIOLATION, which is appropriate for your situation as one of the files could not be accessed.
To skip the offending file, enable the FOF_NOERRORUI flag in the SHFILEOPSTRUCT::fFlags field. The SHFILEOPSTRUCT documentation says only the following about that flag:
FOF_NOERRORUI
Do not display a dialog to the user if an error occurs.
However, the documentation does also say this:
fAnyOperationsAborted
Type: BOOL
When the function returns, this member contains TRUE if any file operations were aborted before they were completed; otherwise, FALSE. An operation can be manually aborted by the user through UI or it can be silently aborted by the system if the FOF_NOERRORUI or FOF_NOCONFIRMATION flags were set.
The documentation for IFileOperation (which replaces SHFileOperation() on Vista and later) has more to say about the FOF_NOERRORUI flag:
FOF_NOERRORUI (0x0400)
Do not display a message to the user if an error occurs. If this flag is set without FOFX_EARLYFAILURE, any error is treated as if the user had chosen Ignore or Continue in a dialog box. It halts the current action, sets a flag to indicate that an action was aborted, and proceeds with the rest of the operation.
...
FOFX_EARLYFAILURE (0x00100000)
If FOFX_EARLYFAILURE is set together with FOF_NOERRORUI, the entire set of operations is stopped upon encountering any error in any operation. This flag is valid only when FOF_NOERRORUI is set.
So, with FOF_NOERRORUI enabled, the return value of ERROR_SHARING_VIOLATION, and also the SHFILEOPSTRUCT::fAnyOperationsAborted field being set to TRUE, will tell you that a file could not be accessed during the copy, but not which file specifically. It does not mean that the entire SHFileOperation() task failed completely.

How can I programmatically determine if path is protected? [duplicate]

I need to know if a specified directory (local or shared path with login credentials) has write permissions or not.
I am using GetFileAttributes but it always returns FILE_ATTRIBUTE_DIRECTORY and nothing else.
My code is something like below
if(storageLocation != "")
{
//! check if local storage - user name password would be empty
if(storageUsername == "" && storagePassword == "")
{
//! local storage
//! lets check whether the local path is a valid path or not
boost::filesystem::path fpath(storageUsername.c_str());
if(boost::filesystem::exists(fpath))
{
DWORD attrib = ::GetFileAttributes(storageLocation.c_str());
if((attrib != INVALID_FILE_ATTRIBUTES) &&
((attrib & FILE_ATTRIBUTE_READONLY) != FILE_ATTRIBUTE_READONLY))
{
string strWritePermission = "TRUE";
}
}
}
else
{
uncLocation_t uncLocation;
uncLocation.m_location = storageLocation;
uncLocation.m_username = storageUsername;
uncLocation.m_password = storagePassword;
if(0 == connectToUNCLocation(uncLocation)) // My function to connect to UNC location
{
//! successful connection
DWORD attrib = ::GetFileAttributes(storageLocation.c_str());
if((attrib != INVALID_FILE_ATTRIBUTES) &&
((attrib & FILE_ATTRIBUTE_READONLY) != FILE_ATTRIBUTE_READONLY))
{
string strWritePermission = "TRUE";
}
}
}
}
I don't understand why but GetFileAttributes always return 0x16.
I have tested it by creating a shared folder and creating 2 folders in it. One with read only permissions and other with default permissions. But in all 3 cases (shared folder, read only folder and default permission folder) I am getting same return value.
There is on way to find write permission, to create a temporary file (usinf CreateFile in GENERIC_WRITE mode) and if successfully created, delete it. But I don't want to use this method as I don't want my application to create a temporary file each time user specifies a location.
Please suggest what should be done.
You would need to replicate the security checking that Windows performs. The AccessCheck function will help that. You are currently well wide of the mark in looking at the file attributes. Windows security is so much more complicated than that.
Although you said you did not want to do it, the right solution is not to try to check. Simply do whatever it is you are attempting to do. If the system decides that the user does not have sufficient rights, then CreateFile will fail, and the last error will be set to ERROR_ACCESS_DENIED. There's no need for temporary files. You just try to do whatever it is you are doing, and let it fail. You have to handle failure anyway since there are many ways for a file operation to fail, not just security.
As the saying goes, it is better to ask forgiveness than permission.
I think you are looking for AccessCheck. FYI, this is not a C++ question, but a Windows API question.

Resources