5

I'd like to dup a file descriptor running in an unrelated process on Linux. I know about sendmsg(2) and SCM_RIGHTS (e.g. https://stackoverflow.com/questions/4489433/sending-file-descriptor-over-unix-domain-socket-and-select) , but that works only if the other process is cooperating. I need a solution which doesn't need active cooperation from the other process. I also know that I could create the file descriptor first, keep a copy, and then create the other process, but I need a solution in which the other process creates its own file descriptor.

I can see the file descriptor:

$ ls -l /proc/13115/fd/3
lrwx------ 1 pts pts 64 2013-05-04 13:15 /proc/13115/fd/3 -> socket:[19445454]

However, open("/proc/13115/fd/3", O_RDWR) executed in another process returns the error No such device or address. Is there something else that would work? Probably with ptrace?

pts
  • 1,061
  • 14
  • 23
  • Always try to include error text rather than "an error". – Runium May 04 '13 at 11:42
  • "I need a solution which doesn't need active cooperation from the other process" So the process is supposed to just suddenly have an open file without having done anything? – Bratchley May 04 '13 at 11:50
  • Do you have permissions to access this processes file descriptor? I guess I'm asking if both processes are owned by the same userid. or at least the 2nd one has access to first user's file perms? – slm May 04 '13 at 11:51
  • @JoelDavis: Not like that. The other process has been running for a while, and it has already created the file descriptor for itself. I know the PID and the fd number. I want to get the file descriptor. I don't care about race conditions. – pts May 04 '13 at 11:55
  • @slm: Yes, I have permissions, all UIDs are the same. – pts May 04 '13 at 11:56
  • Curious, what if the file descriptor wasn't a socket but an actual file, would that change things? – slm May 04 '13 at 12:12
  • Sorry, I originally said in my answer that if it relates to a socket you are out of luck, but that is not strictly true: you can sniff packets, although this is not nearly the same as duplicating a descriptor. – goldilocks May 04 '13 at 12:31
  • I'm not interested in sniffing packets. I need a duplicate of the socket file descriptor. – pts May 04 '13 at 13:19
  • 2
    Yes, execute sendmsg in process 13115 by injecting code via ptrace. – Gilles 'SO- stop being evil' May 04 '13 at 14:12
  • @Gilles: Can you point me to a working implementation of that? I couldn't find one, and I'd like to reuse a good one if possible, because I have the impression that using ptrace naively is very error-prone and unstable -- and I don't want to make the other process crash. – pts May 04 '13 at 19:13

2 Answers2

4

You can't do this for the same reason you can't access the memory of an unrelated process, because a file descriptor is part of the memory of another process. The only reason there's information about stuff like this in /proc is because the kernel provides it there, and it is read-only (thus there are means to examine a copy of process memory).

If it relates to a file, you can of course try and access the file. If it's a socket, you could snoop on it using libpcap or something derived from it.

The situation is basically this: a file descriptor is (again) part of a process's memory. The descriptor has an underlying buffer which exists in kernel space; when the process reads or writes to/from the descriptor, it is writing to and from this buffer. For data going out, the kernel flushes the buffer out (to hardware) appropriately; for data coming in, it refills the buffer (from hardware) when the process empties it. Those buffers are not, AFAIK, accessible to other processes, although there are some means (eg. libpcap) of reading the data in some form determined by the particular kernel interface, just as the proc interface can provide some data from the process's user space memory.

goldilocks
  • 87,661
  • 30
  • 204
  • 262
  • 1
    Thank you for composing your answer. What you are saying (you can't access the memory of an unrelated process) is incorrect, because ptrace(2) can do it. Are you sure that what I need can't be done? If so, what is your justification? http://stackoverflow.com/questions/11214066/why-does-this-sendmsg-call-i-injected-via-ptrace-not-work ? – pts May 04 '13 at 13:16
  • 1
    @pts I'm pretty sure but I do not have enough granular knowledge of the kernel to prove it definitively. I've added a paragraph outlining my reasoning. By "access memory" I meant directly -- ptrace uses a kernel interface to get a copy of the data. – goldilocks May 04 '13 at 13:46
  • 1
    Thank you for the clarification. FYI I can't give +1 to your answer because the beginning (you can't access the memory of an unrelated process) is still incorrect, because ptrace(2) can do it. You are also incorrect about writing the memory of another process: /proc/13115/mem is writable, and it affects the other process. I fail to see why your explanation about buffers makes the socket impossible to dup: in fact, sendmsg(2) can do it using SCM_RIGHTS, also it gets duplicated upon fork(2). – pts May 04 '13 at 13:52
  • @pts Fair enough, although I'll stand by what I've said about the difference between accessing a copy via a kernel interface and accessing the memory. man proc notes that /proc/[n]/mem "can be used to access the pages of a process's memory through open(2), read(2), and lseek(2)". That the handle is rw does not mean writing to it is effective (I'm very dubious about your claim that "it affects other processes"...you mean process 13115? How so?): http://lwn.net/Articles/432347/ although evidently the kernel could be patched to make it so. – goldilocks May 04 '13 at 14:01
  • 4
    @pts goldilocks is correct: a process cannot access the memory of another process. A process can make the kernel access the memory of another process on its behalf, with ptrace. Access to another process's /proc/$pid/mem requires setup with ptrace first. – Gilles 'SO- stop being evil' May 04 '13 at 14:11
  • @goldilocks, @Gilles: Yes, you are correct that /proc/$pid/mem is read-only. I've just tried it, and reading /proc/$pid/mem has worked, but writing it has failed with Invalid argument (even as root). Thank you for the clarification! – pts May 04 '13 at 19:22
  • 1
    @pts Using /proc/$pid/mem is tricky — see How do I read from /proc/$pid/mem under Linux?. It's not the easiest way to do what you want though. – Gilles 'SO- stop being evil' May 05 '13 at 18:28
  • 2
    The file descriptor is actually in kernel memory, and often is shared between processes, after being inherited from parent to child. And @pts, you can write to another process's memory with /proc/$pid/mem ( without ptrace ), you just can't write to invalid areas of it. You can even mmap() it so there's no copying going on. – psusi May 06 '13 at 23:39
4

This is by design: sharing file descriptors with other processes is explicit. By default, file descriptors are as private as the process's own memory.

As usual, if you have the right to ptrace the process, you can do anything you like, including making it call sendmsg. Traditionally, calling ptrace requires running as the same user ID; security restrictions such as SELinux, capabilities, jails and so on can make ptrace more restrictive. For example, under the default Ubuntu configuration, a non-root process can only call ptrace on its own descendants (through AppArmor).

Using ptrace robustly is a bit tricky: you have to inject the right data, make sure not to overwrite anything, and clean up after yourself. So my recommendation is to inject the code in a roundabout way, and trigger that code with an existing tool.

Write a small shared library containing the sendmsg code, and LD_PRELOAD it to the other process. Here's some untested skeleton code with error checking missing.

int pts_gift_fd (char *path, int fd) {
    int sock;
    struct sockaddr_un addr = {0};
    struct msghdr msg = {0};
    struct iovec iov = {0};
    addr.sun_family = AF_UNIX;
    strlcpy(addr.sun_path, path, sizeof(addr.sun_path));
    /* Populate msg, iov as in the code you've already found */
    sock = socket(AF_UNIX, SOCK_STREAM, 0);
    connect(sock, (struct sockaddr*)&addr, sizeof(addr));
    sendmsg(sock, &msg, 0);
    close(sock);
}

Then, to trigger the code, run gdb -n -pid 13115 -batch -x /dev/stdin with popen and feed it input like this (where %d is the fd you want to obtain, and %s is a path to a unix socket that you've previously created and are listening on):

call pts_gift_fd("%s", %d)
detach
quit
pts
  • 1,061
  • 14
  • 23