Created
January 11, 2024 09:35
-
-
Save allisonkarlitskaya/7a80f2ebb3314d80f45c653a1ba0e398 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Research: what happens with /proc/self/fd/x when x is an fd that was | |
* opened with O_NOFOLLOW. | |
* | |
* ie: can we inotify symlinks using systemd? (it does the same thing) | |
* | |
* This program exits cleanly (ie: all asserts pass). | |
* | |
* Results: | |
* | |
* - operations that normally don't follow symlinks: work on the /proc | |
* file itself. That's expected. | |
* | |
* - operations that normally do follow symlinks: follow the first | |
* level, but not the next level (ie: end up on the original symlink | |
* itself). This is unexpected (since normally a symlink pointing to | |
* a symlink is dereferenced recursively) but very useful. | |
* | |
* One special case: it's not possible to do a readlink() in a | |
* meaningful way, but since Linux 2.6.39, you can use readlinkat() | |
* directly on the fd. | |
*/ | |
#define _GNU_SOURCE | |
#include <assert.h> | |
#include <errno.h> | |
#include <fcntl.h> | |
#include <limits.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <sys/inotify.h> | |
#include <sys/poll.h> | |
#include <sys/stat.h> | |
int | |
main (void) | |
{ | |
struct stat a_buf; | |
symlink ("b", "a"); | |
int a_fd = open ("a", O_PATH | O_NOFOLLOW); | |
int b_fd = creat ("b", 0666); | |
assert (a_fd == 3); | |
const char *a_path = "/proc/self/fd/3"; | |
/* For comparison... */ | |
int r = fstat(a_fd, &a_buf); | |
assert (r == 0 && a_buf.st_mode & S_IFLNK); | |
/*** Non-dereferencing operations ***/ | |
{ | |
/* lstat() — stats the symlink living in /proc */ | |
{ | |
struct stat buf; | |
int r = lstat (a_path, &buf); | |
assert (r == 0); | |
assert (buf.st_mode & S_IFLNK); | |
assert (buf.st_dev != a_buf.st_dev); | |
} | |
/* open(O_PATH | O_NOFOLLOW) — opens the symlink living in /proc */ | |
{ | |
struct stat buf; | |
int path_fd = open(a_path, O_PATH | O_NOFOLLOW); | |
int r = fstat (path_fd, &buf); | |
assert (r == 0); | |
assert (buf.st_mode & S_IFLNK); | |
assert (buf.st_dev != a_buf.st_dev); | |
close (path_fd); | |
} | |
/* open(O_RDONLY | O_NOFOLLOW) — can't open a symlink for read */ | |
{ | |
int path_fd = open(a_path, O_RDONLY); | |
assert (path_fd == -1 && errno == ELOOP); | |
} | |
/* readlink() — reads the link to "/path/to/a" */ | |
{ | |
char target[PATH_MAX]; | |
int r = readlink (a_path, target, sizeof target); | |
assert (r != -1); | |
/* make sure the path name ends with 'a' */ | |
assert (r > 0); | |
assert (target[r - 1] == 'a'); | |
} | |
} | |
/*** Standard dereferencing operations ***/ | |
{ | |
/* stat() — stats the symlink "a" */ | |
{ | |
struct stat buf; | |
int r = stat (a_path, &buf); | |
assert (r == 0); | |
assert (buf.st_mode & S_IFLNK); | |
assert (buf.st_dev == a_buf.st_dev); | |
} | |
/* open(O_PATH) — opens the symlink "a" */ | |
{ | |
struct stat buf; | |
int path_fd = open(a_path, O_PATH); | |
int r = fstat (path_fd, &buf); | |
assert (r == 0); | |
assert (buf.st_mode & S_IFLNK); | |
assert (buf.st_dev == a_buf.st_dev); | |
close (path_fd); | |
} | |
/* open(O_RDONLY) — can't open a symlink for read */ | |
{ | |
int path_fd = open(a_path, O_RDONLY); | |
assert (path_fd == -1 && errno == ELOOP); /* ie: open(/proc/self/fd/3, O_RDONLY) can't open the symlink */ | |
} | |
/* It's not possible to read "b" via proc, but we can do this: */ | |
{ | |
char target[PATH_MAX]; | |
/* | |
* readlinkat(2): | |
* | |
* Since Linux 2.6.39, pathname can be an empty string, in which | |
* case the call operates on the symbolic link referred to by | |
* dirfd (which should have been obtained using open(2) with the | |
* O_PATH and O_NOFOLLOW flags). | |
*/ | |
int r = readlinkat (a_fd, "", target, sizeof target); | |
assert (r == 1); | |
assert (memcmp (target, "b", 1) == 0); | |
} | |
} | |
/*** inotify ***/ | |
{ | |
int inotify_fd = inotify_init (); | |
{ | |
int wd1 = inotify_add_watch (inotify_fd, a_path, IN_ALL_EVENTS | IN_DONT_FOLLOW); | |
assert (wd1 == -1); | |
assert (errno == EACCES); | |
} | |
{ | |
int wd2 = inotify_add_watch (inotify_fd, a_path, IN_ALL_EVENTS); | |
assert (wd2 == 1); | |
} | |
struct pollfd pfd[] = { { .fd = inotify_fd, .events = POLLIN } }; | |
{ | |
int r = poll (pfd, 1, 0); | |
assert (r == 0); /* no events yet */ | |
} | |
/* Ensure we're really watching "a", and not "b" */ | |
{ | |
unlink("b"); | |
int r = poll (pfd, 1, 0); | |
assert (r == 0); /* no events yet */ | |
} | |
{ | |
unlink("a"); | |
int r = poll (pfd, 1, 0); | |
assert (r == 1); /* no events yet */ | |
} | |
close (inotify_fd); | |
} | |
close (a_fd); | |
close (b_fd); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment