Skip to content

Instantly share code, notes, and snippets.

@allisonkarlitskaya
Created January 11, 2024 09:35
Show Gist options
  • Save allisonkarlitskaya/7a80f2ebb3314d80f45c653a1ba0e398 to your computer and use it in GitHub Desktop.
Save allisonkarlitskaya/7a80f2ebb3314d80f45c653a1ba0e398 to your computer and use it in GitHub Desktop.
/* 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