flink (AT_EMPTY_PATH / AT_SYMLINK_FOLLOW) security considerations

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Linux 3.10 (and many earlier kernels) allow flink using an incantation
like linkat(AT_FDCWD, "/proc/self/fd/N", destdirfd, newname,
AT_SYMLINK_FOLLOW);  It's possible to do much the same thing using
linkat(oldfd, "", destdirfd, newname, AT_EMPTY_PATH) if you're
privileged on 3.10, and the requirement for privilege is dropped in
3.11-rc5.

The immediate motivation for dropping the privilege requirement is the
O_TMPFILE changes: you can create a temporary file with O_TMPFILE,
write to it, and then give it a name with linkat(..., AT_EMPTY_PATH).
You can prevent this behavior by using O_TMPFILE | O_EXCL.

Apparently there's some kind of new security issue here [1], but I
don't know what it is.  So I'd like to get other people's thoughts.

Some notes:

All linkat variations do this:

	/* Make sure we don't allow creating hardlink to an unlinked file */
	if (inode->i_nlink == 0 && !(inode->i_state & I_LINKABLE))
		error =  -ENOENT;

That means that deleted files (except for O_TMPFILE, which sets
I_LINKABLE) can't be flinked.

Both flink variants work on O_PATH fds.

I've attached some test code if you want to play with this stuff.

Possible changes include inspecting f_cred before flink, requiring
I_ILINKABLE if unprivileged, and reverting the 3.11 change.

[1] https://lwn.net/Articles/562488/ -- see the comments
#include <stdio.h>
#include <err.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define __O_TMPFILE 020000000
#define O_TMPFILE (__O_TMPFILE | O_DIRECTORY)
#define AT_EMPTY_PATH 0x1000

int main(int argc, char **argv)
{
  char buf[128];

  if (argc != 4)
    errx(1, "Usage: flinktest TMPDIR PATH linkat|proc");

  int fd = open(argv[1], O_TMPFILE | O_RDWR, 010600);
  if (fd == -1)
    err(1, "O_TMPFILE");
  write(fd, "test", 4);

  if (!strcmp(argv[3], "linkat")) {
    if (linkat(fd, "", AT_FDCWD, argv[2], AT_EMPTY_PATH) != 0)
      err(1, "linkat");
  } else if (!strcmp(argv[3], "proc")) {
    sprintf(buf, "/proc/self/fd/%d", fd);
    if (linkat(AT_FDCWD, buf, AT_FDCWD, argv[2], AT_SYMLINK_FOLLOW) != 0)
      err(1, "linkat");
  } else {
    errx(1, "invalid mode");
  }
  return 0;
}
#include <stdio.h>
#include <err.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define AT_EMPTY_PATH 0x1000
#ifndef O_PATH
#define O_PATH          010000000
#endif

int main(int argc, char **argv)
{
  char buf[128];

  if (argc != 5)
    errx(1, "Usage: flinktest OLDPATH NEWPATH <normal|O_PATH> AT_EMPTY_PATH|proc");

  int flag;
  if (!strcmp(argv[3], "normal"))
    flag = O_RDONLY;
  else if (!strcmp(argv[3], "O_PATH"))
    flag = O_PATH;
  else
    errx(1, "bad open mode");

  int fd = open(argv[1], flag);
  if (fd == -1)
    err(1, "open");

  if (!strcmp(argv[4], "AT_EMPTY_PATH")) {
    if (linkat(fd, "", AT_FDCWD, argv[2], AT_EMPTY_PATH) != 0)
      err(1, "linkat");
  } else if (!strcmp(argv[4], "proc")) {
    sprintf(buf, "/proc/self/fd/%d", fd);
    if (linkat(AT_FDCWD, buf, AT_FDCWD, argv[2], AT_SYMLINK_FOLLOW) != 0)
      err(1, "linkat");
  } else {
    errx(1, "invalid mode");
  }
  return 0;
}

[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [Samba]     [Device Mapper]     [CEPH Development]
  Powered by Linux