From patchwork Thu Aug 13 09:32:45 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Drysdale X-Patchwork-Id: 7007161 Return-Path: X-Original-To: patchwork-linux-fsdevel@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 55D8B9F344 for ; Thu, 13 Aug 2015 09:33:59 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 1BAF320652 for ; Thu, 13 Aug 2015 09:33:58 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id A089D20445 for ; Thu, 13 Aug 2015 09:33:56 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752347AbbHMJdy (ORCPT ); Thu, 13 Aug 2015 05:33:54 -0400 Received: from mail-wi0-f178.google.com ([209.85.212.178]:34811 "EHLO mail-wi0-f178.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752371AbbHMJc4 (ORCPT ); Thu, 13 Aug 2015 05:32:56 -0400 Received: by wicne3 with SMTP id ne3so251351334wic.1 for ; Thu, 13 Aug 2015 02:32:55 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=Kx9nn+A0A1UmUV11XvxwqrPUardVBR8it8/iG3rrpE4=; b=C25yShh3qjNtejxXNmm5lHcN+vcYPq1EjTwtuEE364jrwcFgTJfBaZgF+xRvw8P6NP NCfB6L71UvuqCiuqd9/elwhKYcf3f3dnsrszlzS1Dj++SDZlM6Q4rcnmMmGVXpHzZK7N 1JIragNtDDcPjAZ2Qwh+L16fwnr4RPS3oU3uc7+/xLJcQgYd/ueqtEjmFFHaVBle2QAG ofbS6LEbflizHynrGLZWQxnGhK6tms+B6mScJq5y+f/6SatScCOpybOHVHyFV5Jj7u/c B+A0tymIQdzx6f9U/AlyW5Z5BKCgj6IzLB04KlTRuQWIy8AEEcwJXzmFkhv5J9c9+zOt 2KXg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=Kx9nn+A0A1UmUV11XvxwqrPUardVBR8it8/iG3rrpE4=; b=XbCpqZ9gCDSgfZW8DSAJnAfVdCcBarcRhN8Rtj+J1WAo1C+1FtGOyTR5Fo+I7WVS1Z ONvkuXOhQAG5AfY2z5FrIMqybERaAADHNPPpJAKUKJo+/CvYSj4TAbBgrai8UOyWwmJV GuhoZFPWtglLGJBgfMogk0E2bKmBBn4mKtaupCY32KgT9h3JXze3jxPR/M3jPhGfS/nq ln9MR6DkjkzLyetbqkBPuxzPsd0yWWLQXRb3CiJoBg4vqB+YVQcn+9lWRb2uhss+0d7G Vt9UZbAb/YKpIVRAN3sHI/z/bT7NK3ZzW0TE90QximIc/I1CssPZHZbrm8NYezUMesPS bEIw== X-Gm-Message-State: ALoCoQl3uH8ZhP+K5gdY+llQ2+LCPs7jkn6A1U+nKu8rf+0tSj7giNLZ1fzR4l5xzOqZNJlxm+kY X-Received: by 10.180.211.11 with SMTP id my11mr52279841wic.51.1439458375226; Thu, 13 Aug 2015 02:32:55 -0700 (PDT) Received: from localhost.localdomain ([74.125.61.146]) by smtp.gmail.com with ESMTPSA id jr5sm2439204wjc.14.2015.08.13.02.32.53 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Thu, 13 Aug 2015 02:32:54 -0700 (PDT) From: David Drysdale To: linux-kernel@vger.kernel.org, Alexander Viro , Kees Cook , "Eric W. Biederman" Cc: Greg Kroah-Hartman , Meredydd Luff , Will Drewry , Jorge Lucangeli Obes , Ricky Zhou , Lee Campbell , Julien Tinnes , Mike Depinet , James Morris , Andy Lutomirski , Paolo Bonzini , Paul Moore , Christoph Hellwig , Michael Kerrisk , Dave Chinner , linux-api@vger.kernel.org, linux-arch@vger.kernel.org, linux-security-module@vger.kernel.org, linux-fsdevel@vger.kernel.org, fstests@vger.kernel.org, David Drysdale Subject: [PATCHv4 2/3] selftests: Add test of O_BENEATH & openat(2) Date: Thu, 13 Aug 2015 10:32:45 +0100 Message-Id: <1439458366-8223-3-git-send-email-drysdale@google.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1439458366-8223-1-git-send-email-drysdale@google.com> References: <1439458366-8223-1-git-send-email-drysdale@google.com> Sender: linux-fsdevel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-fsdevel@vger.kernel.org X-Spam-Status: No, score=-6.8 required=5.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD, T_DKIM_INVALID, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Add simple tests of openat(2) variations, including examples that check the new O_BENEATH flag. Signed-off-by: David Drysdale --- tools/testing/selftests/Makefile | 1 + tools/testing/selftests/openat/.gitignore | 4 + tools/testing/selftests/openat/Makefile | 29 ++++ tools/testing/selftests/openat/openat.c | 258 ++++++++++++++++++++++++++++++ 4 files changed, 292 insertions(+) create mode 100644 tools/testing/selftests/openat/.gitignore create mode 100644 tools/testing/selftests/openat/Makefile create mode 100644 tools/testing/selftests/openat/openat.c diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 24ae9e829e9a..606f8df5c8aa 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -11,6 +11,7 @@ TARGETS += memory-hotplug TARGETS += mount TARGETS += mqueue TARGETS += net +TARGETS += openat TARGETS += powerpc TARGETS += ptrace TARGETS += seccomp diff --git a/tools/testing/selftests/openat/.gitignore b/tools/testing/selftests/openat/.gitignore new file mode 100644 index 000000000000..835b2dcd8678 --- /dev/null +++ b/tools/testing/selftests/openat/.gitignore @@ -0,0 +1,4 @@ +openat +subdir +topfile +symlinkdown \ No newline at end of file diff --git a/tools/testing/selftests/openat/Makefile b/tools/testing/selftests/openat/Makefile new file mode 100644 index 000000000000..73f80428b6a5 --- /dev/null +++ b/tools/testing/selftests/openat/Makefile @@ -0,0 +1,29 @@ +CFLAGS = -Wall +BINARIES = openat +DEPS = subdir topfile symlinkdown subdir/bottomfile subdir/symlinkup subdir/symlinkout subdir/symlinkin +all: $(BINARIES) $(DEPS) + +subdir: + mkdir -p subdir +topfile: + echo 0123456789 > $@ +subdir/bottomfile: | subdir + echo 0123456789 > $@ +subdir/symlinkup: | subdir + ln -s ../topfile $@ +subdir/symlinkout: | subdir + ln -s /etc/passwd $@ +subdir/symlinkin: | subdir + ln -s bottomfile $@ +symlinkdown: + ln -s subdir/bottomfile $@ +%: %.c + $(CC) $(CFLAGS) -o $@ $^ + +TEST_PROGS := openat +TEST_FILES := $(DEPS) + +include ../lib.mk + +clean: + rm -rf $(BINARIES) $(DEPS) diff --git a/tools/testing/selftests/openat/openat.c b/tools/testing/selftests/openat/openat.c new file mode 100644 index 000000000000..bc2c4b02a091 --- /dev/null +++ b/tools/testing/selftests/openat/openat.c @@ -0,0 +1,258 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Bypass glibc */ +static int openat_(int dirfd, const char *pathname, int flags) +{ + return syscall(__NR_openat, dirfd, pathname, flags); +} +static int open_(const char *pathname, int flags) +{ + return syscall(__NR_open, pathname, flags); +} + +static int openat_or_die(int dfd, const char *path, int flags) +{ + int fd = openat_(dfd, path, flags); + + if (fd < 0) { + printf("Failed to openat(%d, '%s'); " + "check prerequisites are available\n", dfd, path); + exit(1); + } + return fd; +} + +static int check_fd(int fd) +{ + int rc; + struct stat info; + char buffer[4]; + + if (fd < 0) { + printf("[FAIL]: openat() failed, rc=%d errno=%d (%s)\n", + fd, errno, strerror(errno)); + return 1; + } + if (fstat(fd, &info) != 0) { + printf("[FAIL]: fstat() failed, rc=%d errno=%d (%s)\n", + fd, errno, strerror(errno)); + return 1; + } + if (!S_ISDIR(info.st_mode)) { + errno = 0; + rc = read(fd, buffer, sizeof(buffer)); + if (rc < 0) { + printf("[FAIL]: read() failed, rc=%d errno=%d (%s)\n", + rc, errno, strerror(errno)); + return 1; + } + } + close(fd); + printf("[OK]\n"); + return 0; +} + +static int check_openat(int dfd, const char *path, int flags) +{ + int fd; + + errno = 0; + printf("Check success of openat(%d, '%s', %x)... ", + dfd, path?:"(null)", flags); + fd = openat_(dfd, path, flags); + return check_fd(fd); +} + +static int check_open(const char *path, int flags) +{ + int fd; + + errno = 0; + printf("Check success of open('%s', %x)... ", path?:"(null)", flags); + fd = open_(path, flags); + return check_fd(fd); +} + +static int check_fail(int rc, int expected_errno, const char *errno_str) +{ + if (rc > 0) { + printf("[FAIL] (unexpected success from open operation)\n"); + close(rc); + return 1; + } + if (errno != expected_errno) { + printf("[FAIL] (expected errno %d (%s) not %d (%s)\n", + expected_errno, strerror(expected_errno), + errno, strerror(errno)); + return 1; + } + printf("[OK]\n"); + return 0; +} + +#define check_openat_fail(dfd, path, flags, errno) \ + _check_openat_fail(dfd, path, flags, errno, #errno) +static int _check_openat_fail(int dfd, const char *path, int flags, + int expected_errno, const char *errno_str) +{ + int rc; + + printf("Check failure of openat(%d, '%s', %x) with %s... ", + dfd, path?:"(null)", flags, errno_str); + errno = 0; + rc = openat_(dfd, path, flags); + return check_fail(rc, expected_errno, errno_str); +} + +#define check_open_fail(path, flags, errno) \ + _check_open_fail(path, flags, errno, #errno) +static int _check_open_fail(const char *path, int flags, + int expected_errno, const char *errno_str) +{ + int rc; + + printf("Check failure of open('%s', %x) with %s... ", + path?:"(null)", flags, errno_str); + errno = 0; + rc = open_(path, flags); + return check_fail(rc, expected_errno, errno_str); +} + +int check_proc(void) +{ + int root_dfd = openat_(AT_FDCWD, "/", O_RDONLY); + int proc_dfd = openat_(AT_FDCWD, "/proc/self", O_RDONLY); + int fail = 0; + + if (proc_dfd < 0) { + printf("'/proc/self' unavailable (errno=%d '%s'), skipping\n", + errno, strerror(errno)); + return 0; + } + fail += check_openat(proc_dfd, "root/etc/passwd", O_RDONLY); + fail += check_openat(root_dfd, "proc/self/root/etc/passwd", O_RDONLY); +#ifdef O_BENEATH + fail += check_openat_fail(proc_dfd, "root/etc/passwd", + O_RDONLY|O_BENEATH, EPERM); + fail += check_openat_fail(root_dfd, "proc/self/root/etc/passwd", + O_RDONLY|O_BENEATH, EPERM); +#endif + return fail; +} + +int main(int argc, char *argv[]) +{ + int fail = 0; + int dot_dfd = openat_or_die(AT_FDCWD, ".", O_RDONLY); + int subdir_dfd = openat_or_die(AT_FDCWD, "subdir", O_RDONLY); + int file_fd = openat_or_die(AT_FDCWD, "topfile", O_RDONLY); + + /* Sanity check normal behavior */ + fail += check_open("topfile", O_RDONLY); + fail += check_open("subdir/bottomfile", O_RDONLY); + fail += check_openat(AT_FDCWD, "topfile", O_RDONLY); + fail += check_openat(AT_FDCWD, "subdir/bottomfile", O_RDONLY); + + fail += check_openat(dot_dfd, "topfile", O_RDONLY); + fail += check_openat(dot_dfd, "subdir/bottomfile", O_RDONLY); + fail += check_openat(dot_dfd, "subdir/../topfile", O_RDONLY); + fail += check_open("subdir/../topfile", O_RDONLY); + + fail += check_openat(subdir_dfd, "../topfile", O_RDONLY); + fail += check_openat(subdir_dfd, "bottomfile", O_RDONLY); + fail += check_openat(subdir_dfd, "../subdir/bottomfile", O_RDONLY); + fail += check_openat(subdir_dfd, "symlinkup", O_RDONLY); + fail += check_openat(subdir_dfd, "symlinkout", O_RDONLY); + + fail += check_open("/etc/passwd", O_RDONLY); + fail += check_openat(AT_FDCWD, "/etc/passwd", O_RDONLY); + fail += check_openat(dot_dfd, "/etc/passwd", O_RDONLY); + fail += check_openat(subdir_dfd, "/etc/passwd", O_RDONLY); + + fail += check_openat_fail(AT_FDCWD, "bogus", O_RDONLY, ENOENT); + fail += check_openat_fail(dot_dfd, "bogus", O_RDONLY, ENOENT); + fail += check_openat_fail(999, "bogus", O_RDONLY, EBADF); + fail += check_openat_fail(file_fd, "bogus", O_RDONLY, ENOTDIR); + +#ifdef O_BENEATH + /* Test out O_BENEATH */ + fail += check_open("topfile", O_RDONLY|O_BENEATH); + fail += check_open("subdir/bottomfile", O_RDONLY|O_BENEATH); + fail += check_openat(AT_FDCWD, "topfile", O_RDONLY|O_BENEATH); + fail += check_openat(AT_FDCWD, "subdir/bottomfile", + O_RDONLY|O_BENEATH); + + fail += check_openat(dot_dfd, "topfile", O_RDONLY|O_BENEATH); + fail += check_openat(dot_dfd, "subdir/bottomfile", + O_RDONLY|O_BENEATH); + fail += check_openat(dot_dfd, "subdir///bottomfile", + O_RDONLY|O_BENEATH); + fail += check_openat(subdir_dfd, "bottomfile", O_RDONLY|O_BENEATH); + fail += check_openat(subdir_dfd, "./bottomfile", O_RDONLY|O_BENEATH); + fail += check_openat(subdir_dfd, ".", O_RDONLY|O_BENEATH); + + /* Symlinks without .. or leading / are OK */ + fail += check_open("symlinkdown", O_RDONLY|O_BENEATH); + fail += check_open("subdir/symlinkin", O_RDONLY|O_BENEATH); + fail += check_openat(dot_dfd, "symlinkdown", O_RDONLY|O_BENEATH); + fail += check_openat(dot_dfd, "subdir/symlinkin", O_RDONLY|O_BENEATH); + fail += check_openat(subdir_dfd, "symlinkin", O_RDONLY|O_BENEATH); + /* ... unless of course we specify O_NOFOLLOW */ + fail += check_open_fail("symlinkdown", + O_RDONLY|O_BENEATH|O_NOFOLLOW, ELOOP); + fail += check_open_fail("subdir/symlinkin", + O_RDONLY|O_BENEATH|O_NOFOLLOW, ELOOP); + fail += check_openat_fail(dot_dfd, "symlinkdown", + O_RDONLY|O_BENEATH|O_NOFOLLOW, ELOOP); + fail += check_openat_fail(dot_dfd, "subdir/symlinkin", + O_RDONLY|O_BENEATH|O_NOFOLLOW, ELOOP); + fail += check_openat_fail(subdir_dfd, "symlinkin", + O_RDONLY|O_BENEATH|O_NOFOLLOW, ELOOP); + + /* Can't open paths with ".." in them */ + fail += check_open_fail("subdir/../topfile", + O_RDONLY|O_BENEATH, EPERM); + fail += check_openat_fail(dot_dfd, "subdir/../topfile", + O_RDONLY|O_BENEATH, EPERM); + fail += check_openat_fail(subdir_dfd, "../topfile", + O_RDONLY|O_BENEATH, EPERM); + fail += check_openat_fail(subdir_dfd, "../subdir/bottomfile", + O_RDONLY|O_BENEATH, EPERM); + fail += check_openat_fail(subdir_dfd, "..", O_RDONLY|O_BENEATH, EPERM); + + /* Can't open paths starting with "/" */ + fail += check_open_fail("/etc/passwd", O_RDONLY|O_BENEATH, EPERM); + fail += check_openat_fail(AT_FDCWD, "/etc/passwd", + O_RDONLY|O_BENEATH, EPERM); + fail += check_openat_fail(dot_dfd, "/etc/passwd", + O_RDONLY|O_BENEATH, EPERM); + fail += check_openat_fail(subdir_dfd, "/etc/passwd", + O_RDONLY|O_BENEATH, EPERM); + /* Can't sneak around constraints with symlinks */ + fail += check_openat_fail(subdir_dfd, "symlinkup", + O_RDONLY|O_BENEATH, EPERM); + fail += check_openat_fail(subdir_dfd, "symlinkout", + O_RDONLY|O_BENEATH, EPERM); + fail += check_openat_fail(subdir_dfd, "../symlinkdown", + O_RDONLY|O_BENEATH, EPERM); + fail += check_openat_fail(dot_dfd, "subdir/symlinkup", + O_RDONLY|O_BENEATH, EPERM); + fail += check_open_fail("subdir/symlinkup", O_RDONLY|O_BENEATH, EPERM); +#else + printf("Skipping O_BENEATH tests due to missing #define\n"); +#endif + fail += check_proc(); + + if (fail > 0) + printf("%d tests failed\n", fail); + return fail; +}