Very weird issue with AuthorizationExecuteWithPriveleges in Cocoa - objective-c

I'm using AuthorizationExecuteWithPriveleges to execute bash commands from my App with admin privilege. I have found really weird issue. Here what I'm using
FILE *pipe=nil;
OSStatus err;
AuthorizationRef authorizationRef;
char *command= "/bin/chmod";
char *args[] = {"644","folderPath", nil};
if(err!=0)
{
err = AuthorizationCreate(nil,
kAuthorizationEmptyEnvironment,
kAuthorizationFlagDefaults,
&authorizationRef);
}
NSLog(#"test");
err = AuthorizationExecuteWithPrivileges(authorizationRef,
command,
kAuthorizationFlagDefaults,
args,
&pipe);
After calling this function about 40 times, it's starting respond very slowly. And after it is will just die,and freeze application, and I have no idea what is happening to this.It doesn't show the log "test", and doesn't do anything, after calling about 40 times.
It doesn't matter what Bash command or what arguments you are using. It still does the same thing. What is wrong with this ? The reason I'm using this, because my App needs to run on 10.5 as well.
Please if someone have idea, what can I do. I really appreciate it. I need ASAP. Thanks

Looked at this a bit more, and cooked up the following example, presented without warranty, but which works for me for thousands of invocations of AuthorizationExecuteWithPrivileges without issue:
void DoOtherStuff(AuthorizationRef auth, char* path);
void DoStuff(char* path)
{
AuthorizationItem foo;
foo.name = kAuthorizationRightExecute;
foo.value = NULL;
foo.valueLength = 0;
foo.flags = 0;
AuthorizationRights rights;
rights.count = 1;
rights.items = &foo;
AuthorizationRef authorizationRef;
OSStatus err = errAuthorizationSuccess;
if (errAuthorizationSuccess != (err = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authorizationRef)))
{
NSLog(#"Error on AuthorizationCreate: %lu", (long)err);
return;
}
for (NSUInteger i = 0; i < 5000; i++)
{
NSLog(#"Doing run: %lu", (long)i+1);
DoOtherStuff(authorizationRef, "/tmp/foo");
}
if (errAuthorizationSuccess != (err = AuthorizationFree(authorizationRef, kAuthorizationFlagDefaults)))
{
NSLog(#"Error on AuthorizationFree: %lu", (long)err);
return;
}
}
void DoOtherStuff(AuthorizationRef authorizationRef, char* path)
{
OSStatus err = errAuthorizationSuccess;
FILE *pipe = NULL;
#try
{
char *args[] = {"644", path, NULL};
if (errAuthorizationSuccess != (err = AuthorizationExecuteWithPrivileges(authorizationRef,
"/bin/chmod", kAuthorizationFlagDefaults, args, &pipe)))
{
NSLog(#"Error on AuthorizationExecuteWithPrivileges: %lu", (long)err);
return;
}
int stat;
wait(&stat);
NSLog(#"Success! Child Process Died!");
}
#finally
{
if (pipe)
fclose(pipe);
}
}
What Chris Suter said is dead on. What happens when you call AuthorizationExecuteWithPrivileges is that it fork()s your process and then exec()s the requested process (chmod in this case) from the child process. The child process won't be reaped until someone calls wait(), but that's hard because we don't get the PID of the child out of AuthorizationExecuteWithPrivileges (it would have been returned by fork()). As he said, if you're sure there aren't other threads spawning processes at the same time (i.e. your thread is the only one creating child processes), then you can just call the non-PID specific version of wait() like I do in this example.
If you don't call wait() then what happens is you accumulate these zombie child processes that are all waiting to be reaped. Eventually the OS says "no more."
I feel kinda bad posting this, since it's just a retread of what Chris Suter said; I've upvoted his answer.
For completeness, here's a reworked version of that example that achieves the goal by ignoring SIGCHLD instead of calling wait. It also is presented without warranty, but works for me.
void DoOtherStuff(AuthorizationRef auth, char* path);
void DoStuff(char* path)
{
AuthorizationItem foo;
foo.name = kAuthorizationRightExecute;
foo.value = NULL;
foo.valueLength = 0;
foo.flags = 0;
AuthorizationRights rights;
rights.count = 1;
rights.items = &foo;
AuthorizationRef authorizationRef;
OSStatus err = errAuthorizationSuccess;
struct sigaction oldAction;
struct sigaction newAction;
newAction.__sigaction_u.__sa_handler = SIG_IGN;
newAction.sa_mask = 0;
newAction.sa_flags = 0;
if(0 != sigaction(SIGCHLD, &newAction, &oldAction))
{
NSLog(#"Couldn't ignore SIGCHLD");
return;
}
#try
{
if (errAuthorizationSuccess != (err = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authorizationRef)))
{
NSLog(#"Error on AuthorizationCreate: %lu", (long)err);
return;
}
for (NSUInteger i = 0; i < 1000; i++)
{
NSLog(#"Doing run: %lu", (long)i+1);
DoOtherStuff(authorizationRef, "/tmp/foo");
}
if (errAuthorizationSuccess != (err = AuthorizationFree(authorizationRef, kAuthorizationFlagDefaults)))
{
NSLog(#"Error on AuthorizationFree: %lu", (long)err);
return;
}
}
#finally
{
const struct sigaction cOldAction = oldAction;
if(0 != sigaction(SIGCHLD, &cOldAction, NULL))
{
NSLog(#"Couldn't restore the handler for SIGCHLD");
return;
}
}
}
void DoOtherStuff(AuthorizationRef authorizationRef, char* path)
{
OSStatus err = errAuthorizationSuccess;
FILE *pipe = NULL;
#try
{
char *args[] = {"644", path, NULL};
if (errAuthorizationSuccess != (err = AuthorizationExecuteWithPrivileges(authorizationRef,
"/bin/chmod", kAuthorizationFlagDefaults, args, &pipe)))
{
NSLog(#"Error on AuthorizationExecuteWithPrivileges: %lu", (long)err);
return;
}
NSLog(#"Success!");
}
#finally
{
if (pipe)
fclose(pipe);
}
}

What you’re trying to do is not a good idea.
I would guess that you have a bug else where in your code, perhaps in the monitoring of the pipe. We need to see the rest of your code.
If you do pursue this approach, you will need to take care and make sure that you clean up zombie processes which can be awkward when using AuthorizationExecuteWithPrivileges because you don’t get the child process ID. You’ll either need to ignore SIGCHLD, or if you can be certain there are no other threads that are doing things with processes at the same time, you can just issue a call to wait.
You’ve also got to make sure you clean up the pipe as otherwise you’ll run out of file descriptors.
The system is far less forgiving about you leaking file descriptors or processes than it is about leaking memory.
The correct approach for your problem is probably to write a helper tool and then communicate with your helper tool asking it to perform privileged operations on your behalf. That way you’ll only be running your helper tool once. You should be able to read more about this in Apple’s documentation.

You should initialize err (due to the first IF statement), because it's no guaranteed to be 0. However, it probably is, so you are skipping AuthorizationCreate, so the authorized session isn't created.
Basically you are passing authorizationRef uninitialized to AuthorizationExecuteWithPrivileges which might be a problem.
Plus like others, I would put AuthorizationFree(authorizationRef,kAuthorizationFlagDefaults); at the end as well when you do use AuthorizationCreate to free the memory.
Also it's worth noting that AuthorizationExecuteWithPrivileges is deprecated as of OS X v10.7, but I think you know that since you said you are trying to run on 10.5
EDIT: You might want to check the status of err too after running AuthorizationCreate
if ( err != errAuthorizationSuccess ) {
return;
}
... you should check err after AuthorizationExecuteWithPrivileges as well.

I think I might know what's going on here: Try handling the pipe correctly (i.e. don't pass NULL, and make sure you close it). Weird stuff like this also happens with NSTask if you fail to give it a STDIN pipe. This page over at cocoadev.com explains:
An NSTask will break Xcode's debug log entirely if you execute
ANYTHING related with sh or bash (including scripts). printf, NSLog;
all will cease to function as soon as the task has been launched. Even
things like right clicking on an object in the debugger will yield
nothing (straight GDB still prints though). ... I figured out that the
problem lies with standard input, of all things. A quick fix for this
would be to set your standard input up to something random, like a
pipe, and do nothing with it.
This stumped me for hours and hours (albiet with NSTask and not AS). I would be surprised if the odd behavior you're seeing isn't related. Make sure you're not passing NULL, and then make sure that you're cleaning up the file handle that AuthorizationExecuteWithPrivileges creates for you, by calling fclose, etc.
I'm not 100% sure, but the "NSLog stopped working" symptom caught my eye.

Related

Text Services Framework failed to set global compartment value as VT_BSTR

I wrote a test application (.exe) for inter-process communication using TSF global compartment and the following code works correctly when the variant type is VT_I4, but for VT_BSTR the ITfCompartment::SetValue return S_FALSE and the OnChange callback is not fired on the text service (an IME).
The S_FALSE for ITfCompartment::SetValue is not even documented on MSDN and I guess that means the operation succeeded but has no effect.
Can anyone offer some ideas as to how to solve this problem? Thanks!
ITfThreadMgr *pThreadMgr;
if (FAILED(CoCreateInstance(CLSID_TF_ThreadMgr, nullptr, CLSCTX_INPROC_SERVER, IID_ITfThreadMgr, (void **)&pThreadMgr)))
{
return;
}
if (FAILED(pThreadMgr->Activate(&m_tfClientID)))
{
return;
}
ITfCompartmentMgr *pCompartmentMgr;
if (pThreadMgr->GetGlobalCompartment(&pCompartmentMgr) != S_OK)
{
return;
}
ITfCompartment *pCompartment;
if (pCompartmentMgr->GetCompartment(TheGlobalCompartmentGUID, &pCompartment) != S_OK)
{
pCompartment = nullptr;
pCompartmentMgr->Release();
return;
}
VARIANT varValue;
varValue.vt = VT_BSTR;
varValue.bstrVal = SysAllocString(L"abc");
//varValue.vt = VT_I4;
//varValue.lVal = 1;
HRESULT hr = pCompartment->SetValue(m_tfClientID, &varValue);
if (hr != S_OK)
{
OutputDebugString(L"SetValue failed");
}
pCompartment->Release();
pCompartmentMgr->Release();
The short answer is that you can only store integers in global compartments. Marshaling a string or object is not possible given how TSF global compartments work (it runs below the COM marshaling layer).

How do I create an Inter App MIDI In port

I will program an inter App MIDI In Port in my Arranger App, that can be accessed by other MIDI App's. I would appreciate very much to get some sample code. I built a virtual MIDI In port like this, but how to make it visible for other App's:
MIDIClientRef virtualMidi;
result = MIDIClientCreate(CFSTR("Virtual Client"), MyMIDINotifyProc, NULL, &virtualMidi);
You need to use MIDIDestinationCreate, which will be visible to other MIDI Clients. You need to provide a MIDIReadProc callback that will be notified when a MIDI event arrives to your MIDI Destination. You may create another MIDI Input Port as well, with the same callback, that you can connect yourself from within your own program to an external MIDI Source.
Here is an example (in C++):
void internalCreate(CFStringRef name)
{
OSStatus result = noErr;
result = MIDIClientCreate( name , nullptr, nullptr, &m_client );
if (result != noErr) {
qDebug() << "MIDIClientCreate() err:" << result;
return;
}
result = MIDIDestinationCreate ( m_client, name, MacMIDIReadProc, (void*) this, &m_endpoint );
if (result != noErr) {
qDebug() << "MIDIDestinationCreate() err:" << result;
return;
}
result = MIDIInputPortCreate( m_client, name, MacMIDIReadProc, (void *) this, &m_port );
if (result != noErr) {
qDebug() << "MIDIInputPortCreate() error:" << result;
return;
}
}
Another example, in ObjectiveC from symplesynth
- (id)initWithName:(NSString*)newName
{
PYMIDIManager* manager = [PYMIDIManager sharedInstance];
MIDIEndpointRef newEndpoint;
OSStatus error;
SInt32 newUniqueID;
// This makes sure that we don't get notified about this endpoint until after
// we're done creating it.
[manager disableNotifications];
MIDIDestinationCreate ([manager midiClientRef], (CFStringRef)newName, midiReadProc, self, &newEndpoint);
// This code works around a bug in OS X 10.1 that causes
// new sources/destinations to be created without unique IDs.
error = MIDIObjectGetIntegerProperty (newEndpoint, kMIDIPropertyUniqueID, &newUniqueID);
if (error == kMIDIUnknownProperty) {
newUniqueID = PYMIDIAllocateUniqueID();
MIDIObjectSetIntegerProperty (newEndpoint, kMIDIPropertyUniqueID, newUniqueID);
}
MIDIObjectSetIntegerProperty (newEndpoint, CFSTR("PYMIDIOwnerPID"), [[NSProcessInfo processInfo] processIdentifier]);
[manager enableNotifications];
self = [super initWithMIDIEndpointRef:newEndpoint];
ioIsRunning = NO;
return self;
}
Ports can't be discovered from the API, but sources and destinations can. You want to create a MIDISource or MIDIDestination so that MIDI clients can call MIDIGetNumberOfDestinations/MIDIGetDestination or MIDIGetNumberOfSources/MIDIGetSource and discover it.
FYI, there is no need to do what you are planning to do on macOS because the IAC driver already does it. If this is for iOS, these are the steps to follow:
Create at least one MIDI Client.
Create a MIDIInputPort with a read block for I/O.
Use MIDIPortConnectSource to attach the input port to every MIDI Source of interest.
[From now, every MIDI message received by the source will come to your read block.]
If you want to resend this data to a different destination, you'll need to have created a MIDIOutputPort as well. Use MIDISend with that port to the desired MIDI Destination.

Given a Process ID, determine if the process is a windowed process on a Mac

Need a way to programmatically determine if a process is a windowed process using the process ID. This needs to work for both user and system processes.
With the crude method below, one could determine if a user process is windowed. However, this has a major flaw, it will only work for user processes, not system.
- (BOOL)processIsWindowed:(pid_t)processID {
for (NSRunningApplication app in [[NSWorkspace sharedWorkspace] runningApplications]) {
if(app.processIdentifier == processID && (app.activationPolicy == NSApplicationActivationPolicyRegular)){
return YES;
}
}
return NO;
}
Using:
static int GetBSDProcessList(kinfo_proc **procList, size_t *procCount){}
from
Using NSWorkspace to get all running processes
will list all processes, but I can't immediatgely see a way to determine if it is a windowed process.
A process listed by the above method has flags (i.e. process->kp_proc.p_flags) but I don't see any flags listed: https://opensource.apple.com/source/xnu/xnu-1456.1.26/bsd/sys/proc.h that might indicate it as a windowed process.
Here's how you can determine if a process has a window:
The UiProcesses() method will create an array of processIDs for processes with windows.
CFArrayRef UiProcesses()
{
CFArrayRef orderedwindows = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID);
CFIndex count = CFArrayGetCount (orderedwindows);
CFMutableArrayRef uiProcess = CFArrayCreateMutable (kCFAllocatorDefault , count, &kCFTypeArrayCallBacks);
for (CFIndex i = 0; i < count; i++)
{
if (orderedwindows)
{
CFDictionaryRef windowsdescription = (CFDictionaryRef)CFArrayGetValueAtIndex(orderedwindows, i);
CFNumberRef windowownerpid = (CFNumberRef)CFDictionaryGetValue (windowsdescription, CFSTR("kCGWindowOwnerPID"));
CFArrayAppendValue (uiProcess, windowownerpid);
}
}
return uiProcess;
}
Source: How to Identify if the process in User Interface Process?

Passing a pipefd through execlp in C

I have looked all over and I cannot seem to figure out how to do this.
I have a parent process that has created a pipe()
Now, I want to fork() the parent and then execlp() and pass the pipe() to the new program as a command line argument.
Then from inside the new program I need to be able to read the pipefd.
I've seen a bunch of stuff on how to do it from inside the same process, but nothing on how to do it like this.
Edit: Initial post is/was rather vague.
What I have so far is:
int pfd[2];
if(pipe(pfd) == -1) {
perror("Creating pipe\n");
exit(1);
}
pid_t pid = fork();
if(pid == -1) {
fprintf (stderr, "Initiator Error Message : fork failed\n");
return -1;
}
else if(pid == 0) { // child process
close(pipe0[1]); // close(write);
execlp("program", "program", pipe0[0], NULL);
}
but then I don't really understand what I should do from inside "program" to get the FD. I tried assigning it to all sorts of things, but they all seem to error.
Thank you in advance!
The forked and execed child automatically inherit the open pipe descriptors and the pipe output is usually fed as standard input so that a command line argument to find the pipe is pretty redundant:
if(!pipe(&pipefd))
switch(fork()) {
case 0: !dup2(pipefd[0],0)&&
execlp("cat","cat","-n","/dev/fd/0",0);
case -1: return perror("fork");
default: write(pipefd[1],"OK\n",3);
}

dispatch_source_get_data does not return correct flag when monitoring a directory

Can someone please tell me what is this code not working ? it always return a DISPATH_VNODE_WRITE while monitoring application documents directory in iOS 6.0. (iPad) Below is my code. It returns 0x2 always no matter a file is deleted or renamed or added :(. Is this because it is a directory that I am monitoring ? not a file !!! is there any way I can find out what caused the directory to send notification ?
int directoryFileDescripter = open([documentDirectory UTF8String], O_EVTONLY);
if (directoryFileDescripter < 0) {
NSLog(#"Couldn't obtain file descripter from the system.");
return;
}
dispatch_queue_t mainQueue = /*dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);*/dispatch_get_main_queue();
if (mainQueue == NULL) {
NSLog(#"Couldn't obtain mainQueue from the system.");
close(directoryFileDescripter);
return;
}
dispSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, directoryFileDescripter, DISPATCH_VNODE_WRITE | DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME, mainQueue);
if (dispSource == NULL) {
NSLog(#"Couldn't obtain dispatch source for directory from the system.");
close(directoryFileDescripter);
return;
}
dispatch_source_set_event_handler(dispSource, ^{
NSLog(#"directory notification received.");
int fileDes = dispatch_source_get_handle(dispSource);
unsigned long mask = dispatch_source_get_data(dispSource);
char path[PATH_MAX] = {0};
int nRes = fcntl(fileDes, F_GETPATH, &path);
if (nRes < 0) {
return;
}
if (mask & DISPATCH_VNODE_WRITE) {
NSLog(#"A file has been written.");
}
if (mask & DISPATCH_VNODE_DELETE) {
NSLog(#"A file has been deleted.");
}
if (mask & DISPATCH_VNODE_RENAME) {
NSLog(#"A file has been renamed.");
}
});
dispatch_source_set_cancel_handler(dispSource, ^{
close(directoryFileDescripter);
});
dispatch_resume(dispSource);
You are correct about the "why." When files are created, renamed, or deleted, the directory is modified. You are watching the directory, so you get a "WRITE" event.
I would typically deal with this by re-scanning the directory each time it is marked as written, and noting the changes yourself. If you're not worried about the directory itself moving or being deleted, you can just watch for WRITE events.
You can of course also watch each file's VNODE, but I expect this would be much more complicated to implement well for this kind of problem.