Monday, October 30, 2006

UPDATE: The char *apple[] Argument Vector

Way back in February I posted about the the char *apple[] argument vector, i.e. the "secret" 4th parameter to all binaries executed on Mac OS X. I just wanted to update that post with one small piece of information.

The value of apple[0] isn't always the path to the executed binary image on disk. Specifically, if a symlink points to the file, apple[0] will refer to the symlink. For example:

$ cat -n apple.c
1 #include <stdio.h>
2 int main(int argc, char *argv[], char *envp[], char *apple[]) {
3 printf("apple[0] = %s\n", apple[0]);
4 return 0;
5 }
$ gcc -o apple apple.c
apple[0] = ./apple
$ ln -s apple foo
$ ./foo
apple[0] = ./foo


Ideally, what we'd like is the path to the executing binary image, even if it was executed via a symlink. We can do this using apple[0] and the function realpath(3) to resolve all the symlinks. For example:

$ cat -n apple.c 
1 #include <sys/param.h>
2 #include <stdlib.h>
3 #include <stdio.h>
4 int main(int argc, char *argv[], char *envp[], char *apple[]) {
5 char resolved[PATH_MAX];
6 realpath(apple[0], resolved);
7 printf("resolved apple[0] = %s\n", resolved);
8 return 0;
9 }
$ gcc -o apple apple.c
$ ln -s apple foo
$ ./apple
resolved apple[0] = /Users/jgm/apple
$ ./foo
resolved apple[0] = /Users/jgm/apple


That's about it.

30 comments:

Anonymous said...

... assuming the symlink wasn't replaced with something else.

Greg said...

Right, there's certainly a small race condition there. But if we consider the symlink case, we know it was pointing to a valid binary some time in the *very* recent past, because the code in the binary that's looking at apple[0] is executing. But you're right, there is a race condition there from the time the binary is executed through the symlink, the process starts up, control transfers to main, and we call the realpath(3) function. But that's likely a very small window. And while we're at it, it's probably also a good idea to check the return value of realpath(3).

Drew Thaler said...

Heck, by the time you start the binary could also have been unlinked (and/or replaced with another binary, or something else entirely).

The symlink problem makes it unreliable for a setuid tool to use. Even if the setuid tool itself is protected by filesystem permissions, an attacker could just launch it via a symlink that they own and quickly swizzle the links while dyld is still loading it.

So really it's just about as unreliable as argv[0]. It's merely harder to change; not impossible. :-)

Greg said...

Correct, it's certainly not 100% reliable. And it should never be used as a security mechanism. However, in many cases, SUID binaries aren't used, and knowing the executable name may be useful for other reasons. In this case, the "reasonable guess" that apple[0] + realpath() provides can be useful.

Though, I'd love to know of a better way.

Thanks for making this more clear.

Drew Thaler said...

Sure, it's potentially useful in controlled environments. Just wanted to give a warning to Unix newbies who might be reading this. :-)

There's actually a way to do it without needing an extra char *[] argument in main. sysctl(KERN_PROCARGS) will provide the same information. It's just a little trickier to call. You need to call KERN_ARGMAX first, allocate a buffer that big, call KERN_PROCARGS, and parse it appropriately.

It's not for the timid. But "top" does it, and the source has a big comment describing what's going on. So that's a good starting point.

I'm pretty sure this is exactly the same information that's returned by the extra argument vector, btw. So it has the same properties and problems. Could be a symlink, could be no longer valid, etc.

Greg said...

If avoiding the extra char *[] arg in main is a goal, another option is to use the sorta documented _NSGetExecutablePath() function defined in mach-o/dyld.h. Something like this should work:

#include <mach-o/dyld.h>
...
char buf[PATH_MAX];
uint32_t buflen = PATH_MAX;
_NSGetExecutablePath(buf, &buflen);
printf("buf = %s\n", buf);

Ultimately, _NSGetExecutablePath() gets the path from apple[0], but dyld handles that, and you're not required to declare the param in your main() function.

Anonymous said...

NSProcessInfo resolves symbolic links as well!

NSString *pathToCurrentExecutable = [[[NSProcessInfo processInfo] arguments] objectAtIndex: 0];

Greg said...

Hmm, I don't think that's true.

$ cat -n a.m
1 #import <Foundation/Foundation.h>
2 int main(void) {
3 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
4 NSLog(@"path=%@", [[[NSProcessInfo processInfo] arguments] objectAtIndex:0]);
5 [pool release];
6 return 0;
7 }
$ gcc -o a a.m -framework Foundation
$ ./a
2006-11-03 07:56:37.100 a[4428] path=/Users/jgm/a
$ ln -s a foo
$ ./foo
2006-11-03 07:56:42.461 foo[4430] path=/Users/jgm/foo

Anonymous said...

Oh, yes! That's right, symbolic links don't get resolved! (but i got the real path by double-clicking a symbolic link to executable.command; seems to be the exception to the rule).

Anonymous said...

How much security could a file monitoring tool such as http://bjk.sourceforge.net/bubblegum/ add? (It doesn't use man asl though.)

R2K said...
This comment has been removed by a blog administrator.
Renegade said...
This comment has been removed by a blog administrator.
Diane said...
This comment has been removed by a blog administrator.
sr. Playboy said...
This comment has been removed by a blog administrator.
mindbomb said...
This comment has been removed by a blog administrator.
Clint's Blog said...
This comment has been removed by a blog administrator.
SaM-GiRL said...
This comment has been removed by a blog administrator.
Guru-tech said...
This comment has been removed by a blog administrator.
LIVECHAT said...
This comment has been removed by a blog administrator.
Alexandre said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Lil said...
This comment has been removed by a blog administrator.
Arthur Dail said...
This comment has been removed by a blog administrator.
Magsters said...
This comment has been removed by a blog administrator.
morangos com acucar said...
This comment has been removed by a blog administrator.
P.Y.D. said...
This comment has been removed by a blog administrator.
Nithya said...
This comment has been removed by a blog administrator.
ithinkearthisheaven said...
This comment has been removed by a blog administrator.
Harry said...
This comment has been removed by a blog administrator.
A Wandering Feast said...
This comment has been removed by a blog administrator.