1224653Sjonathan/*- 2224653Sjonathan * Copyright (c) 2009-2011 Robert N. M. Watson 3224653Sjonathan * Copyright (c) 2011 Jonathan Anderson 4224653Sjonathan * All rights reserved. 5224653Sjonathan * 6224653Sjonathan * Redistribution and use in source and binary forms, with or without 7224653Sjonathan * modification, are permitted provided that the following conditions 8224653Sjonathan * are met: 9224653Sjonathan * 1. Redistributions of source code must retain the above copyright 10224653Sjonathan * notice, this list of conditions and the following disclaimer. 11224653Sjonathan * 2. Redistributions in binary form must reproduce the above copyright 12224653Sjonathan * notice, this list of conditions and the following disclaimer in the 13224653Sjonathan * documentation and/or other materials provided with the distribution. 14224653Sjonathan * 15224653Sjonathan * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16224653Sjonathan * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17224653Sjonathan * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18224653Sjonathan * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19224653Sjonathan * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20224653Sjonathan * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21224653Sjonathan * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22224653Sjonathan * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23224653Sjonathan * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24224653Sjonathan * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25224653Sjonathan * SUCH DAMAGE. 26224653Sjonathan */ 27224653Sjonathan 28224653Sjonathan/* 29224653Sjonathan * Test whether various operations on capabilities are properly masked for 30224653Sjonathan * various object types. 31224653Sjonathan */ 32224653Sjonathan 33224653Sjonathan#include <sys/cdefs.h> 34224653Sjonathan__FBSDID("$FreeBSD$"); 35224653Sjonathan 36224653Sjonathan#include <sys/param.h> 37224653Sjonathan#include <sys/capability.h> 38224653Sjonathan#include <sys/errno.h> 39224653Sjonathan#include <sys/mman.h> 40224653Sjonathan#include <sys/mount.h> 41224653Sjonathan#include <sys/stat.h> 42224653Sjonathan 43224653Sjonathan#include <err.h> 44224653Sjonathan#include <fcntl.h> 45224910Sjonathan#include <poll.h> 46224653Sjonathan#include <stdio.h> 47224653Sjonathan#include <stdlib.h> 48224660Sjonathan#include <string.h> 49224653Sjonathan#include <unistd.h> 50224653Sjonathan 51224653Sjonathan#include "cap_test.h" 52224653Sjonathan 53224653Sjonathan#define SYSCALL_FAIL(syscall, message) \ 54224653Sjonathan FAIL("%s:\t%s (rights 0x%jx)", #syscall, message, rights) 55224653Sjonathan 56224653Sjonathan/* 57224653Sjonathan * Ensure that, if the capability had enough rights for the system call to 58224653Sjonathan * pass, then it did. Otherwise, ensure that the errno is ENOTCAPABLE; 59224653Sjonathan * capability restrictions should kick in before any other error logic. 60224653Sjonathan */ 61224653Sjonathan#define CHECK_RESULT(syscall, rights_needed, succeeded) do { \ 62224653Sjonathan if ((rights & (rights_needed)) == (rights_needed)) { \ 63224653Sjonathan if (!(succeeded)) \ 64224653Sjonathan SYSCALL_FAIL(syscall, "failed"); \ 65224653Sjonathan } else { \ 66224653Sjonathan if (succeeded) \ 67224653Sjonathan FAILX("%s:\tsucceeded when it shouldn't have" \ 68224653Sjonathan " (rights 0x%jx)", #syscall, rights); \ 69224653Sjonathan else if (errno != ENOTCAPABLE) \ 70224653Sjonathan SYSCALL_FAIL(syscall, "errno != ENOTCAPABLE"); \ 71224653Sjonathan } \ 72224910Sjonathan errno = 0; \ 73224653Sjonathan} while (0) 74224653Sjonathan 75224653Sjonathan/* 76224653Sjonathan * As above, but for the special mmap() case: unmap after successful mmap(). 77224653Sjonathan */ 78224653Sjonathan#define CHECK_MMAP_RESULT(rights_needed) do { \ 79224653Sjonathan if ((rights & (rights_needed)) == (rights_needed)) { \ 80224653Sjonathan if (p == MAP_FAILED) \ 81224653Sjonathan SYSCALL_FAIL(mmap, "failed"); \ 82224653Sjonathan else \ 83224653Sjonathan (void)munmap(p, getpagesize()); \ 84224653Sjonathan } else { \ 85224653Sjonathan if (p != MAP_FAILED) { \ 86224653Sjonathan FAILX("%s:\tsucceeded when it shouldn't have" \ 87224653Sjonathan " (rights 0x%jx)", "mmap", rights); \ 88224653Sjonathan (void)munmap(p, getpagesize()); \ 89224653Sjonathan } else if (errno != ENOTCAPABLE) \ 90224653Sjonathan SYSCALL_FAIL(syscall, "errno != ENOTCAPABLE"); \ 91224653Sjonathan } \ 92224910Sjonathan errno = 0; \ 93224653Sjonathan} while (0) 94224653Sjonathan 95224653Sjonathan/* 96224653Sjonathan * Given a file descriptor, create a capability with specific rights and 97224653Sjonathan * make sure only those rights work. 98224653Sjonathan*/ 99224653Sjonathanstatic int 100224653Sjonathantry_file_ops(int fd, cap_rights_t rights) 101224653Sjonathan{ 102224653Sjonathan struct stat sb; 103224653Sjonathan struct statfs sf; 104224653Sjonathan int fd_cap, fd_capcap; 105224653Sjonathan ssize_t ssize, ssize2; 106224653Sjonathan off_t off; 107224653Sjonathan void *p; 108224653Sjonathan char ch; 109224660Sjonathan int ret, is_nfs; 110224910Sjonathan struct pollfd pollfd; 111224653Sjonathan int success = PASSED; 112224653Sjonathan 113224660Sjonathan REQUIRE(fstatfs(fd, &sf)); 114224660Sjonathan is_nfs = (strncmp("nfs", sf.f_fstypename, sizeof(sf.f_fstypename)) 115224660Sjonathan == 0); 116224660Sjonathan 117224653Sjonathan REQUIRE(fd_cap = cap_new(fd, rights)); 118224653Sjonathan REQUIRE(fd_capcap = cap_new(fd_cap, rights)); 119224653Sjonathan CHECK(fd_capcap != fd_cap); 120224653Sjonathan 121224910Sjonathan pollfd.fd = fd_cap; 122224910Sjonathan pollfd.events = POLLIN | POLLERR | POLLHUP; 123224910Sjonathan pollfd.revents = 0; 124224910Sjonathan 125224653Sjonathan ssize = read(fd_cap, &ch, sizeof(ch)); 126224653Sjonathan CHECK_RESULT(read, CAP_READ | CAP_SEEK, ssize >= 0); 127224653Sjonathan 128224653Sjonathan ssize = pread(fd_cap, &ch, sizeof(ch), 0); 129224653Sjonathan ssize2 = pread(fd_cap, &ch, sizeof(ch), 0); 130224653Sjonathan CHECK_RESULT(pread, CAP_READ, ssize >= 0); 131224653Sjonathan CHECK(ssize == ssize2); 132224653Sjonathan 133224653Sjonathan ssize = write(fd_cap, &ch, sizeof(ch)); 134224653Sjonathan CHECK_RESULT(write, CAP_WRITE | CAP_SEEK, ssize >= 0); 135224653Sjonathan 136224653Sjonathan ssize = pwrite(fd_cap, &ch, sizeof(ch), 0); 137224653Sjonathan CHECK_RESULT(pwrite, CAP_WRITE, ssize >= 0); 138224653Sjonathan 139224653Sjonathan off = lseek(fd_cap, 0, SEEK_SET); 140224653Sjonathan CHECK_RESULT(lseek, CAP_SEEK, off >= 0); 141224653Sjonathan 142224660Sjonathan /* 143224660Sjonathan * Note: this is not expected to work over NFS. 144224660Sjonathan */ 145224653Sjonathan ret = fchflags(fd_cap, UF_NODUMP); 146224660Sjonathan CHECK_RESULT(fchflags, CAP_FCHFLAGS, 147224660Sjonathan (ret == 0) || (is_nfs && (errno == EOPNOTSUPP))); 148224653Sjonathan 149224653Sjonathan ret = fstat(fd_cap, &sb); 150224653Sjonathan CHECK_RESULT(fstat, CAP_FSTAT, ret == 0); 151224653Sjonathan 152224653Sjonathan p = mmap(NULL, getpagesize(), PROT_READ, MAP_SHARED, fd_cap, 0); 153224653Sjonathan CHECK_MMAP_RESULT(CAP_MMAP | CAP_READ); 154224653Sjonathan 155224653Sjonathan p = mmap(NULL, getpagesize(), PROT_WRITE, MAP_SHARED, fd_cap, 0); 156224653Sjonathan CHECK_MMAP_RESULT(CAP_MMAP | CAP_WRITE); 157224653Sjonathan 158224653Sjonathan p = mmap(NULL, getpagesize(), PROT_EXEC, MAP_SHARED, fd_cap, 0); 159224653Sjonathan CHECK_MMAP_RESULT(CAP_MMAP | CAP_MAPEXEC); 160224653Sjonathan 161224653Sjonathan p = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE, MAP_SHARED, 162224653Sjonathan fd_cap, 0); 163224653Sjonathan CHECK_MMAP_RESULT(CAP_MMAP | CAP_READ | CAP_WRITE); 164224653Sjonathan 165224653Sjonathan p = mmap(NULL, getpagesize(), PROT_READ | PROT_EXEC, MAP_SHARED, 166224653Sjonathan fd_cap, 0); 167224653Sjonathan CHECK_MMAP_RESULT(CAP_MMAP | CAP_READ | CAP_MAPEXEC); 168224653Sjonathan 169224653Sjonathan p = mmap(NULL, getpagesize(), PROT_EXEC | PROT_WRITE, MAP_SHARED, 170224653Sjonathan fd_cap, 0); 171224653Sjonathan CHECK_MMAP_RESULT(CAP_MMAP | CAP_MAPEXEC | CAP_WRITE); 172224653Sjonathan 173224653Sjonathan p = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE | PROT_EXEC, 174224653Sjonathan MAP_SHARED, fd_cap, 0); 175224653Sjonathan CHECK_MMAP_RESULT(CAP_MMAP | CAP_READ | CAP_WRITE | CAP_MAPEXEC); 176224653Sjonathan 177224653Sjonathan ret = fsync(fd_cap); 178224653Sjonathan CHECK_RESULT(fsync, CAP_FSYNC, ret == 0); 179224653Sjonathan 180224653Sjonathan ret = fchown(fd_cap, -1, -1); 181224653Sjonathan CHECK_RESULT(fchown, CAP_FCHOWN, ret == 0); 182224653Sjonathan 183224653Sjonathan ret = fchmod(fd_cap, 0644); 184224653Sjonathan CHECK_RESULT(fchmod, CAP_FCHMOD, ret == 0); 185224653Sjonathan 186224653Sjonathan /* XXX flock */ 187224653Sjonathan 188224653Sjonathan ret = ftruncate(fd_cap, 0); 189224653Sjonathan CHECK_RESULT(ftruncate, CAP_FTRUNCATE, ret == 0); 190224653Sjonathan 191224653Sjonathan ret = fstatfs(fd_cap, &sf); 192224653Sjonathan CHECK_RESULT(fstatfs, CAP_FSTATFS, ret == 0); 193224653Sjonathan 194224653Sjonathan ret = fpathconf(fd_cap, _PC_NAME_MAX); 195224653Sjonathan CHECK_RESULT(fpathconf, CAP_FPATHCONF, ret >= 0); 196224653Sjonathan 197224653Sjonathan ret = futimes(fd_cap, NULL); 198224653Sjonathan CHECK_RESULT(futimes, CAP_FUTIMES, ret == 0); 199224653Sjonathan 200224910Sjonathan ret = poll(&pollfd, 1, 0); 201224910Sjonathan if (rights & CAP_POLL_EVENT) 202224910Sjonathan CHECK((pollfd.revents & POLLNVAL) == 0); 203224910Sjonathan else 204224910Sjonathan CHECK((pollfd.revents & POLLNVAL) != 0); 205224653Sjonathan 206224910Sjonathan /* XXX: select, kqueue */ 207224910Sjonathan 208224653Sjonathan close (fd_cap); 209224653Sjonathan return (success); 210224653Sjonathan} 211224653Sjonathan 212224653Sjonathan#define TRY(fd, rights) \ 213224653Sjonathando { \ 214224653Sjonathan if (success == PASSED) \ 215224653Sjonathan success = try_file_ops(fd, rights); \ 216224653Sjonathan else \ 217224653Sjonathan /* We've already failed, but try the test anyway. */ \ 218224653Sjonathan try_file_ops(fd, rights); \ 219224653Sjonathan} while (0) 220224653Sjonathan 221224653Sjonathanint 222224653Sjonathantest_capabilities(void) 223224653Sjonathan{ 224224653Sjonathan int fd; 225224653Sjonathan int success = PASSED; 226224653Sjonathan 227224910Sjonathan fd = open("/tmp/cap_test_capabilities", O_RDWR | O_CREAT, 0644); 228224653Sjonathan if (fd < 0) 229224653Sjonathan err(-1, "open"); 230224653Sjonathan 231224653Sjonathan if (cap_enter() < 0) 232224653Sjonathan err(-1, "cap_enter"); 233224653Sjonathan 234224653Sjonathan /* XXX: Really want to try all combinations. */ 235224653Sjonathan TRY(fd, CAP_READ); 236224653Sjonathan TRY(fd, CAP_READ | CAP_SEEK); 237224653Sjonathan TRY(fd, CAP_WRITE); 238224653Sjonathan TRY(fd, CAP_WRITE | CAP_SEEK); 239224653Sjonathan TRY(fd, CAP_READ | CAP_WRITE); 240224653Sjonathan TRY(fd, CAP_READ | CAP_WRITE | CAP_SEEK); 241224653Sjonathan TRY(fd, CAP_SEEK); 242224653Sjonathan TRY(fd, CAP_FCHFLAGS); 243224653Sjonathan TRY(fd, CAP_IOCTL); 244224653Sjonathan TRY(fd, CAP_FSTAT); 245224653Sjonathan TRY(fd, CAP_MMAP); 246224653Sjonathan TRY(fd, CAP_MMAP | CAP_READ); 247224653Sjonathan TRY(fd, CAP_MMAP | CAP_WRITE); 248224653Sjonathan TRY(fd, CAP_MMAP | CAP_MAPEXEC); 249224653Sjonathan TRY(fd, CAP_MMAP | CAP_READ | CAP_WRITE); 250224653Sjonathan TRY(fd, CAP_MMAP | CAP_READ | CAP_MAPEXEC); 251224653Sjonathan TRY(fd, CAP_MMAP | CAP_MAPEXEC | CAP_WRITE); 252224653Sjonathan TRY(fd, CAP_MMAP | CAP_READ | CAP_WRITE | CAP_MAPEXEC); 253224653Sjonathan TRY(fd, CAP_FCNTL); 254224797Sjonathan TRY(fd, CAP_POST_EVENT); 255224797Sjonathan TRY(fd, CAP_POLL_EVENT); 256224653Sjonathan TRY(fd, CAP_FSYNC); 257224653Sjonathan TRY(fd, CAP_FCHOWN); 258224653Sjonathan TRY(fd, CAP_FCHMOD); 259224653Sjonathan TRY(fd, CAP_FTRUNCATE); 260224653Sjonathan TRY(fd, CAP_FLOCK); 261224653Sjonathan TRY(fd, CAP_FSTATFS); 262224653Sjonathan TRY(fd, CAP_FPATHCONF); 263224653Sjonathan TRY(fd, CAP_FUTIMES); 264224653Sjonathan TRY(fd, CAP_ACL_GET); 265224653Sjonathan TRY(fd, CAP_ACL_SET); 266224653Sjonathan TRY(fd, CAP_ACL_DELETE); 267224653Sjonathan TRY(fd, CAP_ACL_CHECK); 268224653Sjonathan TRY(fd, CAP_EXTATTR_GET); 269224653Sjonathan TRY(fd, CAP_EXTATTR_SET); 270224653Sjonathan TRY(fd, CAP_EXTATTR_DELETE); 271224653Sjonathan TRY(fd, CAP_EXTATTR_LIST); 272224653Sjonathan TRY(fd, CAP_MAC_GET); 273224653Sjonathan TRY(fd, CAP_MAC_SET); 274224653Sjonathan 275224653Sjonathan /* 276224653Sjonathan * Socket-specific. 277224653Sjonathan */ 278224653Sjonathan TRY(fd, CAP_GETPEERNAME); 279224653Sjonathan TRY(fd, CAP_GETSOCKNAME); 280224653Sjonathan TRY(fd, CAP_ACCEPT); 281224653Sjonathan 282224653Sjonathan return (success); 283224653Sjonathan} 284