1 /*- 2 * Copyright (c) 2006 nCircle Network Security, Inc. 3 * All rights reserved. 4 * 5 * This software was developed by Robert N. M. Watson for the TrustedBSD 6 * Project under contract to nCircle Network Security, Inc. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR, NCIRCLE NETWORK SECURITY, 21 * INC., OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 23 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 * 29 * $FreeBSD$ 30 */ 31 32 /* 33 * There are three cases in which the file system will clear the setuid or 34 * setgid bits on a file when running as !root: 35 * 36 * - When the file is chown()'d and either of the uid or the gid is changed. 37 * 38 * - The file is written to succeesfully. 39 * 40 * - An extended attribute of the file is written to successfully. 41 * 42 * Test each case first as root (that flags aren't cleared), and then as 43 * !root, to check they are cleared. 44 */ 45 46 #include <sys/types.h> 47 #include <sys/extattr.h> 48 #include <sys/stat.h> 49 50 #include <err.h> 51 #include <fcntl.h> 52 #include <stdlib.h> 53 #include <string.h> 54 #include <unistd.h> 55 56 #include "main.h" 57 58 static const gid_t gidset[] = {GID_WHEEL, GID_OWNER, GID_OTHER}; 59 60 /* 61 * Confirm that the setuid bit is set on a file. Don't return on failure. 62 */ 63 static void 64 confirm_setuid(char *fpathp, char *test_case) 65 { 66 struct stat sb; 67 68 if (stat(fpathp, &sb) < 0) { 69 warn("%s stat(%s)", test_case, fpathp); 70 (void)seteuid(UID_ROOT); 71 (void)unlink(fpathp); 72 exit(-1); 73 } 74 if (!(sb.st_mode & S_ISUID)) { 75 warnx("case %s stat(%s) not setuid", test_case, fpathp); 76 (void)seteuid(UID_ROOT); 77 (void)unlink(fpathp); 78 exit(-1); 79 } 80 } 81 82 /* 83 * Confirm that the setuid bit is not set on a file. Don't return on failure. 84 */ 85 static void 86 confirm_notsetuid(char *fpathp, char *test_case) 87 { 88 struct stat sb; 89 90 if (stat(fpathp, &sb) < 0) { 91 warn("%s stat(%s)", test_case, fpathp); 92 (void)seteuid(UID_ROOT); 93 (void)unlink(fpathp); 94 exit(-1); 95 } 96 if (sb.st_mode & S_ISUID) { 97 warnx("case %s stat(%s) is setuid", test_case, fpathp); 98 (void)seteuid(UID_ROOT); 99 (void)unlink(fpathp); 100 exit(-1); 101 } 102 } 103 104 #define EA_NAMESPACE EXTATTR_NAMESPACE_USER 105 #define EA_NAME "clearsugid" 106 #define EA_DATA "test" 107 #define EA_SIZE (strlen(EA_DATA)) 108 void 109 priv_vfs_clearsugid(void) 110 { 111 char ch, fpath[1024]; 112 int fd; 113 114 assert_root(); 115 116 /* 117 * Before starting on work, set up group IDs so that the process can 118 * change the group ID of the file without privilege, in order to see 119 * the effects. That way privilege is only required to maintain the 120 * setuid bit. For the chown() test, we change only the group id, as 121 * that can be done with or without privilege. 122 */ 123 if (setgroups(3, gidset) < 0) 124 err(-1, "setgroups(2, {%d, %d})", GID_WHEEL, GID_OWNER); 125 126 /* 127 * chown() with privilege. 128 */ 129 setup_file(fpath, UID_ROOT, GID_WHEEL, 0600 | S_ISUID); 130 if (chown(fpath, -1, GID_OTHER) < 0) 131 warn("chown(%s, -1, %d) as root", fpath, GID_OTHER); 132 confirm_setuid(fpath, "chown as root"); 133 (void)unlink(fpath); 134 135 /* 136 * write() with privilege. 137 */ 138 setup_file(fpath, UID_ROOT, GID_WHEEL, 0600 | S_ISUID); 139 fd = open(fpath, O_RDWR); 140 if (fd < 0) { 141 warn("open(%s) as root", fpath); 142 goto out; 143 } 144 ch = 0; 145 if (write(fd, &ch, sizeof(ch)) < 0) { 146 warn("write(%s) as root", fpath); 147 goto out; 148 } 149 close(fd); 150 confirm_setuid(fpath, "write as root"); 151 (void)unlink(fpath); 152 153 /* 154 * extwrite() with privilege. 155 */ 156 setup_file(fpath, UID_ROOT, GID_WHEEL, 0600 | S_ISUID); 157 if (extattr_set_file(fpath, EA_NAMESPACE, EA_NAME, EA_DATA, EA_SIZE) 158 < 0) { 159 warn("extattr_set_file(%s, user, %s, %s, %d) as root", 160 fpath, EA_NAME, EA_DATA, EA_SIZE); 161 goto out; 162 } 163 confirm_setuid(fpath, "extwrite as root"); 164 (void)unlink(fpath); 165 166 /* 167 * chown() without privilege. 168 */ 169 setup_file(fpath, UID_OWNER, GID_OWNER, 0600 | S_ISUID); 170 set_euid(UID_OWNER); 171 if (chown(fpath, -1, GID_OTHER) < 0) 172 warn("chown(%s, -1, %d) as !root", fpath, GID_OTHER); 173 set_euid(UID_ROOT); 174 confirm_notsetuid(fpath, "chown as !root"); 175 (void)unlink(fpath); 176 177 /* 178 * write() without privilege. 179 */ 180 setup_file(fpath, UID_OWNER, GID_OWNER, 0600 | S_ISUID); 181 set_euid(UID_OWNER); 182 fd = open(fpath, O_RDWR); 183 if (fd < 0) { 184 warn("open(%s) as !root", fpath); 185 goto out; 186 } 187 ch = 0; 188 if (write(fd, &ch, sizeof(ch)) < 0) { 189 warn("write(%s) as !root", fpath); 190 goto out; 191 } 192 close(fd); 193 set_euid(UID_ROOT); 194 confirm_notsetuid(fpath, "write as !root"); 195 (void)unlink(fpath); 196 197 /* 198 * extwrite() without privilege. 199 */ 200 setup_file(fpath, UID_OWNER, GID_OWNER, 0600 | S_ISUID); 201 set_euid(UID_OWNER); 202 if (extattr_set_file(fpath, EA_NAMESPACE, EA_NAME, EA_DATA, EA_SIZE) 203 < 0) { 204 warn("extattr_set_file(%s, user, %s, %s, %d) as !root", 205 fpath, EA_NAME, EA_DATA, EA_SIZE); 206 goto out; 207 } 208 set_euid(UID_ROOT); 209 confirm_notsetuid(fpath, "extwrite as !root"); 210 (void)unlink(fpath); 211 212 out: 213 (void)seteuid(UID_ROOT); 214 (void)unlink(fpath); 215 } 216