6

Note that while I'm asking this in the context of Android, it's more of a general unix question w/ regard to pipe(2) ...

To transfer large amounts of data from one process to another, one can use ParcelFileDescritor.createPipe(), then send the read end of the pipe to another process via binder. ParcelFileDescritor.createPipe() maps directly to the unix pipe(2) system call.

While the FD was transferred securely over binder to the other process, since ultimately the FD is just an int, is it possible that it could be discovered, or even guessed by a malicious process, opened, and read from?

From my reading, it seems like this boils down to security via obscurity. As long as you don't know, and can't guess the FD int value, it's fine. Anonymous pipes don't expose a way to otherwise discover the FD. But it seems theoretically possible that someone could write an app with a large number of threads that continually tries to open ints based on a random int value, maybe exploiting some pattern in which the numbers are chosen and eventually exploit pipe(2).

Onik
  • 19,396
  • 14
  • 68
  • 91
Jeffrey Blattman
  • 22,176
  • 9
  • 79
  • 134
  • you are passing `ParcelFileDescriptor` to the other side, not `int`, `PFD` is not just a simple integer – pskink Oct 28 '16 at 21:31
  • My concern isn't how it gets passed, but whether the underlying file descriptor can be discovered by a malicious process to which it was NOT passed through binder or other means. And anyway, that PFD ultimately wraps a real file descriptor. – Jeffrey Blattman Oct 28 '16 at 21:36
  • http://unix.stackexchange.com/questions/156859/is-the-data-transiting-through-a-pipe-confidential – CommonsWare Oct 28 '16 at 21:42
  • @pskink I appreciate your response, but an FD is just an integer. You don't need an Android `ParcelFileDescriptor` to open it. You can use `fdopen(3)`. All you need is the `int` value of the FD. – Jeffrey Blattman Oct 31 '16 at 14:40
  • i tried `ParcelFileDescriptor#adoptFd(int)` and `ParcelFileDescriptor#fromFd(int)` both throw `java.io.IOException EBADF (Bad file number)` (when passing `PFD` to a service it works ok), but have no time to check it via jni / ndk and `fdopen` – pskink Oct 31 '16 at 15:28
  • ok found out why EBADF, try to `Log.d` the value of `ParcelFileDescriptor#getFd` before passing via Binder and the same on the remote side, you will see they are different which means that on the remote side the new FD was created by a system and you cannot simply pass an int value hoping you can use it – pskink Oct 31 '16 at 15:52
  • @pskink Thanks. Are you suggesting that when we pass a `ParcelFileDescriptor`, it doesn't actually pass the source FD, but it create an intermediary pipe? Still, I don't think it matters who creates the pipe. There's either access control on the pipe, or not. And AFAICT the answer is not. Regardless of who created the FD, what stops it from being discovered? Again, it's an int. – Jeffrey Blattman Oct 31 '16 at 18:52

1 Answers1

8

The Linux kernel keeps track of one file descriptor table (struct fdtable) for each process (more or less). The entries in this table are indexed by small integers —starting with 0, 1, 2, etc., new entries are given the smallest available integer— and each points to one open file (struct file).

A file in the Linux kernel is a handle to an inode (struct inode) and some state (such as seek position).

If you open the same file multiple times, you will have multiple entries in the file descriptor table, each pointing to different file structures, each pointing to the same inode structure.

If you open a file, and then dup the file descriptor, you will have multiple entries in the file descriptor table, each pointing to the same file structure.

Creating a pipe results in two file descriptors: the read end and the write end. They're somewhat magical: reading from the first file descriptor will return data that was written into the second file descriptor. At the time of creation, both ends of the pipe are only accessible to this process.

Passing a file descriptor to another process (which is normally done by sendmsg over an AF_UNIX domain socket with auxiliary SCM_RIGHTS attached, but on Android is done by Binder.transact with a Parcel.writeFileDescriptor) results in a new entry being added to the receiving process's file descriptor table, pointing at the same file structure as the original entry in the sending process's file descriptor table. NB: The integer index for the same file in the two processes is not related; in fact, it is likely to be different.

Normally in C, you would use fopen to obtain a FILE * structure that you can fread/fwrite/etc. on. The C runtime library does this by opening a file descriptor and wrapping it a structure (that holds additional buffering, etc.). fdopen takes a file descriptor that's already open in the local process, and a FILE * structure around it.

Putting the pieces together:

No other process can open a file by guessing the FD number, because those numbers only have meaning within a single process.* Passing a file descriptor between processes is secure, mediated by the kernel which is manipulating objects that only the kernel has access to.

*Given appropriate privileges, you can go through the /proc/$PID/fd/$FD pseudo-filesystem to find other processes' file descriptors and re-open them for yourself. However, "appropriate privileges" is "same user or root". On Android, all applications run as different users, and none run as root – this is impossible. Additionally, Android's SELinux policy prevents applications from interacting with the /proc interfaces anyway.

Community
  • 1
  • 1
ephemient
  • 198,619
  • 38
  • 280
  • 391