Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ParcelFileDescritor.createPipe(), aka pipe(2), and security

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).

like image 233
Jeffrey Blattman Avatar asked Oct 28 '16 21:10

Jeffrey Blattman


1 Answers

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.

like image 156
ephemient Avatar answered Nov 09 '22 06:11

ephemient