How can I get dtrace to run the traced command with non-root priviledges?
Asked Answered
K

9

64

OS X lacks linux's strace, but it has dtrace which is supposed to be so much better.

However, I miss the ability to do simple tracing on individual commands. For example, on linux I can write strace -f gcc hello.c to caputre all system calls, which gives me the list of all the filenames needed by the compiler to compile my program (the excellent memoize script is built upon this trick)

I want to port memoize on the mac, so I need some kind of strace. What I actually need is the list of files gcc reads and writes into, so what I need is more of a truss. Sure enough can I say dtruss -f gcc hello.c and get somewhat the same functionality, but then the compiler is run with root priviledges, which is obviously undesirable (apart from the massive security risk, one issue is that the a.out file is now owned by root :-)

I then tried dtruss -f sudo -u myusername gcc hello.c, but this feels a bit wrong, and does not work anyway (I get no a.out file at all this time, not sure why)

All that long story tries to motivate my original question: how do I get dtrace to run my command with normal user privileges, just like strace does in linux ?

Edit: is seems that I'm not the only one wondering how to do this: question #1204256 is pretty much the same as mine (and has the same suboptimal sudo answer :-)

Keitloa answered 9/6, 2010 at 16:36 Comment(0)
H
5

Not an answer to your question but something to know. OpenSolaris solved this issue (partially) with "privileges" - see this page. Even in OpenSolaris, it wouldn't be possible to allow an user, without any extra privileges, to dtruss their own process. The reason is the way dtrace works - it enables probes in the kernel. So allowing a non-privileged user to probe kernel means the user can do lot of unwanted things e.g. sniffing other user's passwd by enabling probes in keyboard driver!

Horick answered 15/1, 2011 at 12:16 Comment(2)
You're probably right. even though gaining root privileges is not an issue here, since on my laptop I can (and have done so) chmod a+s dtrace, still, dtrace is not intended as a "unix power user" tool, but as a "unix administrator" tool. That's why trying to use it from user programs leads to such a contrived situation. Thanks a lot for your answer.Keitloa
Couldn't it have a "restricted mode", where only some probes (like the syscall probes, or userspace probes) would trigger, and only in some processes (those owned by the relevant user), and only some functions would be available: those that can easily be made to only inspect the user's own processes, or which only provide access to information already available to the user in other ways?Willaims
N
59

The easiest way is to use sudo:

sudo dtruss -f sudo -u $USER whoami

Other solution would be to run the debugger first and monitor for new specific processes. E.g.

sudo dtruss -fn whoami

Then in another Terminal simply run:

whoami

Simple as that.

More tricky arguments you can find in the manual: man dtruss


Alternatively you can attach dtruss to the running user process e.g. on Mac:

sudo dtruss -fp PID

or similar on Linux/Unix by using strace:

sudo strace -fp PID

Another hacky trick could be to execute the command and right after that attach to the process. Here are some examples:

sudo true; (./Pages &); sudo dtruss -fp `pgrep -n -x Pages`
sudo true; (sleep 1 &); sudo dtruss -fp `pgrep -n -x sleep`
sudo true; (tail -f /var/log/system.log &); sudo dtruss -fp `pgrep -n -x tail`

Note:

  • first sudo is just for caching the password at the first time of running,

  • this trick doesn't work for quick command lines like ls, date as it takes some time untill debugger will attach to the process,

  • you have to type your command in two places,

  • you can ignore & to run the process to the background, if it's already doing that,

  • after finishing debugging, you'll have to manually kill the background process (e.g. killall -v tail)

Nadiya answered 29/7, 2012 at 2:46 Comment(2)
You are a rockstar for pointing out the -n option for dtruss. This is 100% the correct answer.Godwit
With macOS SIP, dtruss will fail if tracing any system binaries, including sudo. Errors with: "dtrace: failed to execute sudo: Operation not permitted"Hayott
G
9

The -n argument to dtruss will cause dtruss to wait and examine processes that match the argument to -n. The -f option will still work to follow processes forked from the processes matched by -n.

All this means that if you want to dtruss a process (for the sake of argument, let's say it's whoami) running as your nonprivileged user, follow these steps:

  1. Open a root shell
  2. Run dtruss -fn whoami
    • this will sit and wait for a process named "whoami" to exist
  3. Open a nonprivileged shell
  4. Run whoami
    • this will execute and exit normally
  5. Observe system call trace in dtruss window
    • dtruss will not exit on its own — it will continue waiting for matching processes — so break out of it when you're done

This answer duplicates the latter part of @kenorb's response, but it deserves to be a first-class answer.

Godwit answered 2/1, 2014 at 21:28 Comment(0)
M
5

I don't know if you can get dtruss to be as noninvasive as strace.

A variant of the "sudo [to root] dtruss sudo [back to nonroot] cmd" that seems to work better in some quick testing for me is:

sudo dtruss -f su -l `whoami` cd `pwd` && cmd....

The outer sudo is of course so dtruss runs as root.

The inner su is back to me, and with -l it recreates the environment properly, at which point we need to cd back to where we started.

I think "su -l user" is better than "sudo -u user" if you want the environment to be what that user normally gets. That'll be their login environment though; I don't know if there's a good way to let the environment inherit through the two user changes instead.

In your question, one additional complaint that you had about the "sudo dtruss sudo" workaround, other than ugliness, was that "I get no a.out file at all this time, not sure why". I don't know why either, but in my little test script, a "sudo dtruss sudo" variant also failed to write to a test output file, and the "sudo dtruss su" variant above did create the output file.

Mediterranean answered 14/1, 2011 at 0:9 Comment(4)
indeed, thanks for this attempt. still, it looks like I'm going to have to give up on this one. On linux, I used to strace commands from a makefile-replacement script, to spy on which files were being touched by the commands. I wanted to port these scripts to mac os x, but at the end of the day I realize that I don't like the idea of having all my builds involving several levels of sudo just to spy on my own commands.Keitloa
Yeah. Directly trying to make dtruss act as a noninvasive strace replacement seems like barking up the wrong tree for the reasons you describe. Maybe an alternate approach is needed. 2 things that occur to me that might be possible: 1) grab the dtrace source, modify the userlevel frontend (which needs to run as root) so it only starts new processes and only spies on those, add an option to tell it who to start those processes as, then make it setuid owned by root.Mediterranean
And #2 (continuing from previous comment): use dtrace directly the way it was designed to answer the question you want answered, instead of trying to use it as a dropin replacement for strace in your makefiles to watch what the child commands were doing -- can you write dtrace probes outside the make process that identify and annotate what the make process is doing?Mediterranean
Thanks a lot for these comments. As for #1 however, I'm afraid I won't have neither enough time nor enough skill (and by far !) to go this way. Option #2 sounds more realistic to me, and I've been trying to get familiar enough with DTrace to try that. If I can write a DTrace script that runs as a daemon and watches all my build from the outside, then indeed I may get the information I need. It's not clear though how I can notify this daemon that when there are new processes to track. I'll post an update if I make some progress. Thanks again.Keitloa
H
5

Not an answer to your question but something to know. OpenSolaris solved this issue (partially) with "privileges" - see this page. Even in OpenSolaris, it wouldn't be possible to allow an user, without any extra privileges, to dtruss their own process. The reason is the way dtrace works - it enables probes in the kernel. So allowing a non-privileged user to probe kernel means the user can do lot of unwanted things e.g. sniffing other user's passwd by enabling probes in keyboard driver!

Horick answered 15/1, 2011 at 12:16 Comment(2)
You're probably right. even though gaining root privileges is not an issue here, since on my laptop I can (and have done so) chmod a+s dtrace, still, dtrace is not intended as a "unix power user" tool, but as a "unix administrator" tool. That's why trying to use it from user programs leads to such a contrived situation. Thanks a lot for your answer.Keitloa
Couldn't it have a "restricted mode", where only some probes (like the syscall probes, or userspace probes) would trigger, and only in some processes (those owned by the relevant user), and only some functions would be available: those that can easily be made to only inspect the user's own processes, or which only provide access to information already available to the user in other ways?Willaims
G
3

It seems that OS X does not support using dtrace to replicate all the features of strace that you need. However, I'd suggest trying to create a wrapper around suitable syscalls. It looks like DYLD_INSERT_LIBRARIES is the environment variable you want to hack a bit. That's basically the same as LD_PRELOAD for Linux.

A much easier way of doing library function overrides is using the DYLD_INSERT_LIBRARIES environment variable (analogous to LD_PRELOAD on Linux). The concept is simple: at load time the dynamic linker (dyld) will load any dynamic libraries specified in DYLD_INSERT_LIBRARIES before any libraries the executable wants loaded. By naming a function the same as one in a library function it will override any calls to the original.

The original function is also loaded, and can be retrieved using the dlsym(RTLD_NEXT, “function_name”); function. This allows a simple method of wrapping existing library functions.

According to the example by Tom Robinson you may need to set DYLD_FORCE_FLAT_NAMESPACE=1, too.

Copy of the original example (lib_overrides.c) that overrides only fopen:

#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>

// for caching the original fopen implementation
FILE * (*original_fopen) (const char *, const char *) = NULL;

// our fopen override implmentation
FILE * fopen(const char * filename, const char * mode)
{
    // if we haven’t already, retrieve the original fopen implementation
    if (!original_fopen)
        original_fopen = dlsym(RTLD_NEXT, "fopen");

    // do our own processing; in this case just print the parameters
    printf("== fopen: {%s,%s} ==\n", filename, mode);

    // call the original fopen with the same arugments
    FILE* f = original_fopen(filename, mode);

    // return the result
    return f;
}

Usage:

$ gcc -Wall -o lib_overrides.dylib -dynamiclib lib_overrides.c
$ DYLD_FORCE_FLAT_NAMESPACE=1 DYLD_INSERT_LIBRARIES=lib_overrides.dylib command-to-test
Grantham answered 28/8, 2014 at 14:0 Comment(0)
P
2

Disclaimer: this is derived from @kenorb's answer. It has some advantages though: PID is more specific than execname. And we can make a short-lived process wait for DTrace before it begins.

This is a bit race-conditiony, but…

Let's say we want to trace cat /etc/hosts:

sudo true && \
(sleep 1; cat /etc/hosts) &; \
sudo dtrace -n 'syscall:::entry /pid == $1/ {@[probefunc] = count();}' $!; \
kill $!

We use sudo true to make sure that we clear sudo's password prompt before we start running anything time-sensitive.

We start a background process ("wait 1 sec, then do something interesting"). Meanwhile, we start DTrace. We've captured the background process's PID into $!, so we can pass that to DTrace as an arg.

The kill $! runs after we close DTrace. It's not necessary for our cat example (process closes on its own), but it helps us end long-running background processes like ping. Passing -p $! to DTrace is the preferred way to do this, but on macOS apparently requires a code-signed executable.


The other thing you can do is to run the command in a separate shell, and snoop that shell. See my answer.

Patinous answered 25/6, 2017 at 18:51 Comment(0)
R
1

I don't know of a way to run what you want as a normal user, as it seems that dtruss, which uses dtrace requires su privileges.

However, I believe the command you were looking for instead of

dtruss -f sudo -u myusername gcc hello.c

is

sudo dtruss -f gcc hello.c

After typing in your password, dtruss will run dtrace will sudo privileges, and you will get the trace as well as the a.out file.

Sorry I couldn't be of further help.

Rabjohn answered 12/1, 2011 at 8:14 Comment(1)
the problem with actually running the command itself as root (apart from the obvious gigantic security risk) is that sudo's runtime environment (PATH, env variables, permissions) is very different from the normal one, and the impacts dramatically the behaviour of the traced program. Linux strace, on the contrary, is almost transparent in terms of functional behaviour of the traced command.Keitloa
Q
0

I am unable to comment on kenorb's answer due to lack of reputation. But just wanted to add that the -W option will cause dtruss to wait for the named process to start, so that the output doesn't contain lots of unrelated system calls. So

sudo dtruss -fW whoami

allowed me to find my issue, where as things were buried using the -fn options.

Qatar answered 27/7, 2022 at 1:35 Comment(0)
R
-1

I fixed my broken Preview with this general method to diagnose and fix crashing apps in Terminal.app at a bash prompt:

$ sudo dtruss -fn Preview 2>&1 | grep '/Users/'

After entering my password, I launched Preview which promptly crashed while dtruss printed

70256/0x19cd898: chdir("/Users/devon/Library/Containers/com.apple.Preview/Data\0", 0x0, 0x7FFF5EAC4BC8) = 0 0

rather than delete this likely suspect, I renamed it

$ (cd ~/Library/Containers && mv -i com.apple.Preview com.apple.Preview.~NOT~)

Preview now works normally and has created a new, non-toxic version of the directory I got rid of.

You might prefer grep '"/' in case it's a system-wide problem but that spams you with hundreds of innocent items in /usr, /System and so on.

As of MacOSX 11.6 "El Capitan" dtrace/dtruss is crippled by default — see how to fix it.

I use this bash function, e.g., $ d /Applications/Preview.app/Contents/MacOS/Preview

d () 
{ 
    case "$*" in 
        [0-9] | [0-9][0-9] | [0-9][0-9][0-9] | [0-9][0-9][0-9][0-9] | [0-9][0-9][0-9][0-9][0-9])
            dtruss -f -p "$*"
        ;;
        *)
            bash -c 'echo -en "\t <<< press return in this window to run after launching trace in root window like this >>> \t # d $$" >/dev/tty; (read -u3 3</dev/tty); exec "$0" "$@"' "$@"
        ;;
    esac
}

Aeons ago we used Apple computers to push data and Levi's jeans to drive tractors — both have devolved into mere fashion companies. The S-F distopia Idiocracy seems relevant.

Royer answered 10/7, 2018 at 11:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.