3 first, since it's easiest: Can it be done in the kernel? Certainly … in theory, you can do “almost anything” there.
Both #2 and #1 come down to the question as to whether the file descriptor is re-openable.
In the most common case — the fd refers to a regular file stream in the local filesystem, the pathname of which has a directory link to which has not been altered — you can simply open the same pathname from another process. EG: If A opens /home/user/foo.log
read-only, then either A or B can simply open the same pathname read-write in future.
Since you're asking, I'll assume it's not that easy. Perhaps the pathname may have been altered (eg, the file may have been unlinked), or perhaps the fd is a reference to another type of stream, like a shell pipeline, FIFO, or network socket connection.
As you probably noticed, fcntl
does not allow escalation of privileges:
F_SETFL (int)
Set the file status flags to the value specified by arg. File
access mode (O_RDONLY, O_WRONLY, O_RDWR) and file creation
flags (i.e., O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC) in arg are
ignored.
This is, of course, a security precaution to protect against escalation of privileges in a process that may have opened the file under temporarily elevated or changed permissions.
However, it seems that you may have a chance to open a new, duplicate stream based upon a pathname if you know the file descriptor number and the process ID of Process A.
The /proc
filesystem contains virtual file entries which represent open streams. Under /proc/
pid /fd/
fd you can find a pathname to the currently-open stream. Given sufficient permissions, you can open that stream.
reader.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
main (int argc, char** argv) {
FILE* f = fopen("/tmp/foo", "r");
while(1) {
sleep(10);
}
}
writer.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int
main (int argc, char** argv) {
/* sane PID passed in */
if (2 != argc) exit(1);
if (5 > strlen(argv[1])) exit(2);
for(size_t ci = 0; argv[ci]; ++ci) {
if (! ( ('0' <= argv[1][ci]) && (argv[1][ci] <= '9') ) ) exit(3);
}
/* note we know FD=3 so it's hard-coded */
char path[100];
int n = snprintf(path, 99, "/proc/%s/fd/3", argv[1]);
if (n < 0) exit (4);
FILE* f = fopen(path, "rw+");
fprintf(f, "written\n");
exit(0);
}
shell test
⇒ cc reader.c -o reader
⇒ cc writer.c -o writer
⇒ echo XXXXXXXXXXXX > /tmp/foo
⇒ cat /tmp/foo
XXXXXXXXXXXX
⇒ ./reader &
[1] 20709
⇒ ./writer 20709
⇒ cat /tmp/foo
written
XXXX