Obtaining admin privileges to delete files using rm from a Cocoa app - cocoa

I am making a small app that deletes log files. I am using an NSTask instance which runs rm and srm (secure rm) to delete files.
I want to be able to delete files in:
/Library/Logs
~/Library/Logs
The issue is that the user account does not have permissions to access some files in the system library folder, such as the Adobe logs subfolder and others. For example, only the "system" user (group?) has r/w permissions for the Adobe logs folder and its contents, and the current user doesn't even have an entry in the permissions shown in the Get Info window for the folder.
What I want to be able to do exactly:
Obtain admin privileges.
Store the password in the Keychain so the app doesn't have to nag the user each time (Is the storage of the password a bad idea? Is it possible?)
Delete a file whatever the file permissions may be.
I am using NSTask because it offers notifications for task completion, getting text output from the task itself, etc. Would I need to use something else? If so, how could I replicate NSTask's completion notifications and output file handle while running rm and srm with admin privileges?
I am looking for the most secure way to handle the situation. i.e. I don't want my application to become a doorway for privilege escalation attacks.
I looked at the Authorization Services Programming Guide but I am not sure which case fits. At first I thought that AuthorizationExecuteWithPrivileges would be a good idea but after reading more on the subject it looks like this method is not recommended for security reasons.
A detailed answer would be very welcome. I'm sure some of you already had to do something similar and have some code and knowledge to share.
Thanks in advance!
Update:
I am now able to make the authentication dialog pop up and obtain privileges, like so:
OSStatus status;
AuthorizationRef authRef;
status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authRef);
AuthorizationRights authRights;
AuthorizationItem authItems[1];
authItems[0].name = kAuthorizationRightExecute;
authRights.count = sizeof(authItems) / sizeof(authItems[0]);
authRights.items = authItems;
AuthorizationFlags authFlags = kAuthorizationFlagDefaults | kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed;
status = AuthorizationCopyRights(authRef, &authRights, kAuthorizationEmptyEnvironment, authFlags, NULL);
From the looks of it, it seems that the "Factored Application" method looks the most appropriate. The thing is that, to me, rm already seems like an external helper tool. I'm not sure I get the setuid alternative suggested in the documentation. Could I set the setuid bit on rm and run it using the NSTask method I already implemented? This would mean that I wouldn't need to create my own helper tool. Could somebody elaborate on this subject?
I also looked at the BetterAuthorizationSample which is suggested as a more secure and recent alternative to the setuid bit method, but found it awfully complex for such as simple behavior. Any hints?
Thanks in advance for any help!

Perhaps a tad late, but this might be useful for future reference for other people. Most of the code is from this person.
Basically, it has a lot to do with Authorization on the Mac. You can read more about that here and here.
The code, which uses the rm tool:
+ (BOOL)removeFileWithElevatedPrivilegesFromLocation:(NSString *)location
{
// Create authorization reference
OSStatus status;
AuthorizationRef authorizationRef;
// AuthorizationCreate and pass NULL as the initial
// AuthorizationRights set so that the AuthorizationRef gets created
// successfully, and then later call AuthorizationCopyRights to
// determine or extend the allowable rights.
// http://developer.apple.com/qa/qa2001/qa1172.html
status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authorizationRef);
if (status != errAuthorizationSuccess)
{
NSLog(#"Error Creating Initial Authorization: %d", status);
return NO;
}
// kAuthorizationRightExecute == "system.privilege.admin"
AuthorizationItem right = {kAuthorizationRightExecute, 0, NULL, 0};
AuthorizationRights rights = {1, &right};
AuthorizationFlags flags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed |
kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights;
// Call AuthorizationCopyRights to determine or extend the allowable rights.
status = AuthorizationCopyRights(authorizationRef, &rights, NULL, flags, NULL);
if (status != errAuthorizationSuccess)
{
NSLog(#"Copy Rights Unsuccessful: %d", status);
return NO;
}
// use rm tool with -rf
char *tool = "/bin/rm";
char *args[] = {"-rf", (char *)[location UTF8String], NULL};
FILE *pipe = NULL;
status = AuthorizationExecuteWithPrivileges(authorizationRef, tool, kAuthorizationFlagDefaults, args, &pipe);
if (status != errAuthorizationSuccess)
{
NSLog(#"Error: %d", status);
return NO;
}
// The only way to guarantee that a credential acquired when you
// request a right is not shared with other authorization instances is
// to destroy the credential. To do so, call the AuthorizationFree
// function with the flag kAuthorizationFlagDestroyRights.
// http://developer.apple.com/documentation/Security/Conceptual/authorization_concepts/02authconcepts/chapter_2_section_7.html
status = AuthorizationFree(authorizationRef, kAuthorizationFlagDestroyRights);
return YES;
}

I had this headache a few months ago. I was trying to get a shell script running with admin privileges that shutdown my computer at a certain time. I feel your pain.
I used the BetterAuthorizationSample which was a total nightmare to wade through. But I took the most pragmatic route - I didn't bother trying to understand everything that was going on, I just grabbed the guts of the code.
It didn't take me that long to get it doing what I wanted. I can't remember exactly what I altered, but you're welcome to check out my code:
http://github.com/johngallagher/TurnItOff
I hope this helps on your quest for a secure application!

Related

How do you request permissions for iOS?

I know the response to this question is "set it in the info.plist" but if I understand the way this works then if the user rejects the permissions you are requesting they don't get prompted again which effectively bricks your app unless you ...write code to check for permissions. Is that right? So I don't know why everyone acts like it's just automatic on iOS. You still have to check for permissions, right?
Assuming I am right, where can I find documentation on how to do it correctly. I checked this (solution was plugin I don't want to use), this (code is too complicated), this (long-winded non-answer), and this plus a few others from google which point to this plugin which I don't want to use. I just want a link to the documentation on how to check and request permissions on iOS. Is there such a link?
What I have looks like this:
private bool HasLocationPermission()
{
return CoreLocation.CLLocationManager.Status == CoreLocation.CLAuthorizationStatus.Authorized ||
CoreLocation.CLLocationManager.Status == CoreLocation.CLAuthorizationStatus.AuthorizedAlways ||
CoreLocation.CLLocationManager.Status == CoreLocation.CLAuthorizationStatus.AuthorizedWhenInUse;
}
but, of course, that is just for the "Location" permission. I don't see any information about what to check for in the LocationManager documentation. There's something about request rational or something? Where can I find how to do this? No plugins please.
If you study the code in the Permissions Plugin you linked to you can pretty easily deduct what you have to do.
In iOS there are 2 different types of location permissions, for either of them to work, you need to set up some descriptions in your Info.plist, which will be shown when prompted the permission dialog.
Set up either NSLocationAlwaysUsageDescription or NSLocationWhenInUseUsageDescription depending on your needs. The distinction between this mode is. When in use, is for when occasionally needing location services, i.e. for briefly showing a map. While always is typically for apps needing it to track the users location all the time.
The section in the Info.plist will look something like:
<key>NSLocationWhenInUseUsageDescription</key>
<string>Do you want My App to access your location?</string>
As you've already figured out you can use CoreLocation and CLLocationManager to get the current permission status:
var locationManager = new CLLocationManager();
var status = locationManager.Status;
Status will either be:
NotDetermined - the App doesn't have permission yet or maybe never asked for it.
AuthorizedAlways - if you requested Always location and added the key NSLocationAlwaysUsageDescription.
AuthorizedWhenInUse - if you requested When In Use location and added the key NSLocationWhenInUseUsageDescription
Denied - user said "no thank you"
If you want to request the permission, simply call:
locationManager.RequestAlwaysAuthorization(); // for always
locationManager.RequestWhenInUseAuthorization(); // for when in use
You can listen to Authorization changes with the AuthorizationChanged event:
locationManager.AuthorizationChanged += OnAuthorizationChanged;
The CLAuthorizationChangedEventArgs will provide you the new status. You may want to hook up this even before requesting the permission.
private void OnAuthorizationChanged(object sender, CLAuthorizationChangedEventArgs args)
{
if (args.Status == CLAuthorizationStatus.AuthorizedAlways ||
args.Status == CLAuthorizationStatus.AuthorizedWhenInUse)
{
// all green, you are good to start listening to location changes!
}
}
Now you can start listening to location changes:
locationManager.LocationsUpdated += OnLocationsUpdated;
locationManager.StartUpdatingLocation();

Windows-10 CreateFile2 error (ERROR_NOT_SUPPORTED_IN_APPCONTAINER)

CreateFile2 api is returning ERROR_NOT_SUPPORTED_IN_APPCONTAINER when the file is not present/available in the path. My code is as below
CREATEFILE2_EXTENDED_PARAMETERS ms_param = {0};
ms_param.dwSize = sizeof(CREATEFILE2_EXTENDED_PARAMETERS);
ms_param.dwFileAttributes = FILE_ATTRIBUTE_READONLY;
ms_param.dwFileFlags = FILE_FLAG_NO_BUFFERING;
ms_param.dwSecurityQosFlags = SECURITY_DELEGATION;
ms_param.lpSecurityAttributes = NULL;
ms_param.hTemplateFile = NULL;
g_hfile = CreateFile2(filename, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, OPEN_EXISTING, &ms_param);
if (g_hfile == INVALID_HANDLE_VALUE)
{
return GetLastError();
}
I have already looked into this thread:CreateFile2 error in WinRT project (ERROR_NOT_SUPPORTED_IN_APPCONTAINER) , reporting similar issue. The solution suggested there doesnt work for me.
From this msdn page:CreateFile2 If the specified file or device does not exist, the function fails and the last-error code is set to ERROR_FILE_NOT_FOUND (2).But I am getting ERROR_NOT_SUPPORTED_IN_APPCONTAINER error
#Hans Passant's, comment helped me figure out the answer. Since I cannot upvote the comment I am adding it as an answer here:
CreateFile2 is not a way to bypass the sandbox restrictions. You only get access to the directories that the appxmanifest asks permissions for.
After checking his comment, I debugged my app deeper & was able to see that under certain scenarios, app was trying to read/write to files which were outside of accessible directories.

Know if MainApp is launched by HelperApp

I am really stuck here with a simple task, but still cannot get it working.
I've managed to implement 'LaunchAtLogin' feature with the HelperApp as described in this article.
But my application should react differently on launch-by-helper-app and launch-by-user actions. So my task now is to have some kind of flag indicating that MainApp was launched by HelperApp.
I know there are many similar questions, but still none of the solutions works for me. Sandbox seems to cut all the parameters I am trying to send to the MainApp.
Here what I have tried:
- [NSWorkspace - launchApplicationAtURL: options: configuration: error:]
- [NSWorkspace - openURLs: withAppBundleIdentifier: options: additionalEventParamDescriptor: launchIdentifiers:]
- LSOpenApplication()
Until recently I thought I can rely on -psn argument sent by Finder when user manually launches the application. But this argument is sent on 10.8 even when MainApp is launched by HelperApp.
In the article mentioned above, author suggests using [NSWorkspace - launchApplicationAtURL: options: configuration: error:]. Parameters are sent, but nothing arrives to the MainApp.
Has anyone succeeded to accomplish this (or similar) task?
Need help!
Thanks in advance!
After hell of searching and experimenting I am ready to answer my own question, so others can save their time and efforts.
My conclusion is that for now there is no way HelperApp can launch MainApp with some arguments under the Sandbox. At least I have not found any way to do that.
Launch MainApp like this:
[[NSWorkspace sharedWorkspace] launchApplication:newPath];
In MainApp add the following:
Application_IsLaunchedByHelperApp = YES;
ProcessSerialNumber currPSN;
OSStatus err = GetCurrentProcess(&currPSN);
if (!err)
{
// Get information about our process
NSDictionary * currDict = [(NSDictionary *)ProcessInformationCopyDictionary(&currPSN,
kProcessDictionaryIncludeAllInformationMask) autorelease];
// Get the PSN of the app that launched us. Its not really the parent app, in the unix sense.
long long temp = [[currDict objectForKey:#"ParentPSN"] longLongValue];
long long hi = (temp >> 32) & 0x00000000FFFFFFFFLL;
long long lo = (temp >> 0) & 0x00000000FFFFFFFFLL;
ProcessSerialNumber parentPSN = {(UInt32)hi, (UInt32)lo};
// Get info on the launching process
NSDictionary * parentDict = [(NSDictionary*)ProcessInformationCopyDictionary(&parentPSN,
kProcessDictionaryIncludeAllInformationMask) autorelease];
// analyze
// parent app info is not null ?
if (parentDict && parentDict.count > 0)
{
NSString * launchedByAppBundleId = [parentDict objectForKey:#"CFBundleIdentifier"];
if (![launchedByAppBundleId isEqualToString:HELPER_APP_BUNDLE_ID])
{
Application_IsLaunchedByHelperApp = NO;
}
}
}
That's it. Application_IsLaunchedByHelperApp now has correct value.
The solution is not mine. I've found it on the web (cocoabuilder, I guess). Good luck to everyone! And thanks for your attention to my questions.
UPDATE
Looks like there are cases when launched at login app shows launchedByAppBundleId = #"com.apple.loginwindow". So the last part of code will look like this:
//
// analyze
//
// parent app info is not null ?
if (parentDict && parentDict.count > 0)
{
NSString * launchedByAppBundleId = [parentDict objectForKey:#"CFBundleIdentifier"];
if (![launchedByAppBundleId isEqualToString:HELPER_APP_BUNDLE_ID] &&
![launchedByAppBundleId isEqualToString:#"com.apple.loginwindow"])
{
Application_IsLaunchedByHelperApp = NO;
}
}
The place to seek an answer is the Apple Developer Forums - folk there have dealt with all sorts of issues around helper apps and the sandbox. Searching for application groups and custom URL schemes going back, say, 1-2 years may turn up a solution that meets your needs. If you are still stuck post a question on those forums, someone will probably know what you need.
If you are not an Apple Developer so have no access to those forums, or do not intend to distribute via the Mac App Store, then just turn off the sandbox - in its current state this isn't technology you choose to use... HTH.

SecTrustedApplicationCreateFromPath being too smart?

I've got an application that also configures and runs a daemon. I am trying to give both the daemon and the application access permissions to the keychain item. The basic code:
SecKeychainItemRef item;
// create a generic password item
SecTrustedApplicationRef appRef[2];
SecAccessRef ref;
SecTrustedApplicationCreateFromPath( NULL, &appRef[0] );
SecTrustedApplicationCreateFromPath( DAEMON_PATH, &appRef[1] );
CFArrayRef trustList = CFArrayCreate( NULL, ( void *)appRef, sizeof(appRef)/sizeof(*appRef), NULL );
SecAccessCreate( descriptor, trustList, &ref );
SecKeychainItemSetAccess( item, ref );
The keychain entry is created, however the only application listed in the Keychain Access tool as always having access is the main application. Let's call it FOO.app. DAEMON_PATH points to the absolute path of the daemon which is in the application bundle -- call it FOO.daemon.
If I manually go within Keychain Access and select the daemon, it does get added to the list.
Any idea on how to get SecTrustedApplicationCreateFromPath to honor the full/absolute path?
If you need an answer today...
I tried to replace access object for existing keychain item with no success, too. So, I decided to modify existing access object rather than replace it, and this approach works well.
The following pseudocode demonstrates the idea. Declarations, CFRelease()s and error checking are stripped for clarity sake.
SecKeychainItemCopyAccess(item, &accessObj);
SecAccessCopySelectedACLList(accessObj, CSSM_ACL_AUTHORIZATION_DECRYPT, &aclList);
assert(CFArrayGetCount(aclList) == 1);
acl = (SecACLRef)CFArrayGetValueAtIndex(aclList, 0);
SecACLCopySimpleContents(acl, &appList, &desc, &prompt_selector);
SecTrustedApplicationCreateFromPath(MY_APP_PATH, &app);
newAppList = CFArrayCreate(NULL, (const void**)&app, 1, NULL);
SecACLSetSimpleContents(acl, newAppList, desc, &psel);
SecKeychainItemSetAccess(item, accessObj);
I used SecAccessCopySelectedACLList to search for an ACL object with an appropriate authorization tag. You may require some other way for ACL filtering.
The straightforward access object creation must be more tricky, you are to create the same ACL structure as Keychain Access app does, rather than use default SecAccessCreate()'s ACLs. I couldn't cope with that way.

How can I pre-authorize authopen?

I'm using authopen inside one of my programs to modify files owned by root. As can be seen in the screenshot below authopen asks for a admin password. What I'd like to achieve is that the dialog shows my app's name and then passes the authorization to authopen.
Code
Launching authopen which returns an authorized file descriptor.
int pipe[2];
socketpair(AF_UNIX, SOCK_STREAM, 0, pipe);
if (fork() == 0) { // child
// close parent's pipe
close(pipe[0]);
dup2(pipe[1], STDOUT_FILENO);
const char *authopenPath = "/usr/libexec/authopen";
execl(authopenPath,
authopenPath,
"-stdoutpipe",
[self.device.devicePath fileSystemRepresentation],
NULL);
NSLog(#"Fatal error, we should never reach %s:%d", __FILE__, __LINE__);
exit(-1);
} else { // parent
close(pipe[1]);
}
// get file descriptor through sockets
I'd really like not to use AuthorizationExecuteWithPrivileges because then I'd have to get more rights than I want to.
Apple has added an option to authopen in OS X 10.9 Mavericks that allows exactly this. Prior this seemed to be impossible.
-extauth
specifies that authopen should read one AuthorizationExternalForm structure from stdin,
convert it to an AuthorizationRef, and attempt to use it to authorize the open(2)
operation.
The authorization should refer to the sys.openfile right corresponding to the requested operation.
The authorization data will be read before any additional data supplied on stdin, and will
not be included in data written with -w.
I have not used this yet, so I do not have any sample code. If someone has, please add it to this answer.
I think if you give your app path in the 1st arg:
execl(authopenPath,
"app path", // <--
"-stdoutpipe",
[self.device.devicePath fileSystemRepresentation],
NULL);
the dialog will show:
"app namerequires that you type your password"
You need to be looking directly at the security framework, introduced in 10.4, I think, and been the main authorization source since 10.5. OSX still works within PAM (like Linux), but /etc/authorization now supersedes this. Apple does have one or two samples of code on how you could pragmatically create a class/entry for someone to authorize themselves against (or preauthorize/be preauthorized like folks who are allowed to print).
This question is old but it seems to be possible, as explained in this technical note :
Technical Note TN2095 : Authorization for Everyone
http://developer.apple.com/library/mac/#technotes/tn2095/_index.html

Resources