• Christian Brauner's avatar
    open: don't silently ignore unknown O-flags in openat2() · cfe80306
    Christian Brauner authored
    The new openat2() syscall verifies that no unknown O-flag values are
    set and returns an error to userspace if they are while the older open
    syscalls like open() and openat() simply ignore unknown flag values:
    
      #define O_FLAG_CURRENTLY_INVALID (1 << 31)
      struct open_how how = {
              .flags = O_RDONLY | O_FLAG_CURRENTLY_INVALID,
              .resolve = 0,
      };
    
      /* fails */
      fd = openat2(-EBADF, "/dev/null", &how, sizeof(how));
    
      /* succeeds */
      fd = openat(-EBADF, "/dev/null", O_RDONLY | O_FLAG_CURRENTLY_INVALID);
    
    However, openat2() silently truncates the upper 32 bits meaning:
    
      #define O_FLAG_CURRENTLY_INVALID_LOWER32 (1 << 31)
      #define O_FLAG_CURRENTLY_INVALID_UPPER32 (1 << 40)
    
      struct open_how how_lowe32 = {
              .flags = O_RDONLY | O_FLAG_CURRENTLY_INVALID_LOWER32,
      };
    
      struct open_how how_upper32 = {
              .flags = O_RDONLY | O_FLAG_CURRENTLY_INVALID_UPPER32,
      };
    
      /* fails */
      fd = openat2(-EBADF, "/dev/null", &how_lower32, sizeof(how_lower32));
    
      /* succeeds */
      fd = openat2(-EBADF, "/dev/null", &how_upper32, sizeof(how_upper32));
    
    Fix this by preventing the immediate truncation in build_open_flags().
    
    There's a snafu here though stripping FMODE_* directly from flags would
    cause the upper 32 bits to be truncated as well due to integer promotion
    rules since FMODE_* is unsigned int, O_* are signed ints (yuck).
    
    In addition, struct open_flags currently defines flags to be 32 bit
    which is reasonable. If we simply were to bump it to 64 bit we would
    need to change a lot of code preemptively which doesn't seem worth it.
    So simply add a compile-time check verifying that all currently known
    O_* flags are within the 32 bit range and fail to build if they aren't
    anymore.
    
    This change shouldn't regress old open syscalls since they silently
    truncate any unknown values anyway. It is a tiny semantic change for
    openat2() but it is very unlikely people pass ing > 32 bit unknown flags
    and the syscall is relatively new too.
    
    Link: https://lore.kernel.org/r/20210528092417.3942079-3-brauner@kernel.org
    Cc: Christoph Hellwig <hch@lst.de>
    Cc: Aleksa Sarai <cyphar@cyphar.com>
    Cc: Al Viro <viro@zeniv.linux.org.uk>
    Cc: linux-fsdevel@vger.kernel.org
    Reported-by: default avatarRichard Guy Briggs <rgb@redhat.com>
    Reviewed-by: default avatarChristoph Hellwig <hch@lst.de>
    Reviewed-by: default avatarAleksa Sarai <cyphar@cyphar.com>
    Reviewed-by: default avatarRichard Guy Briggs <rgb@redhat.com>
    Signed-off-by: default avatarChristian Brauner <christian.brauner@ubuntu.com>
    cfe80306
open.c 34.1 KB