test_write_disk_perms.c revision 232153
1/*- 2 * Copyright (c) 2003-2007 Tim Kientzle 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25#include "test.h" 26__FBSDID("$FreeBSD: head/contrib/libarchive/libarchive/test/test_write_disk_perms.c 232153 2012-02-25 10:58:02Z mm $"); 27 28#if !defined(_WIN32) || defined(__CYGWIN__) 29 30#define UMASK 022 31 32static long _default_gid = -1; 33static long _invalid_gid = -1; 34static long _alt_gid = -1; 35 36/* 37 * To fully test SGID restores, we need three distinct GIDs to work 38 * with: 39 * * the GID that files are created with by default (for the 40 * current user in the current directory) 41 * * An "alt gid" that this user can create files with 42 * * An "invalid gid" that this user is not permitted to create 43 * files with. 44 * The second fails if this user doesn't belong to at least two groups; 45 * the third fails if the current user is root. 46 */ 47static void 48searchgid(void) 49{ 50 static int _searched = 0; 51 uid_t uid = getuid(); 52 gid_t gid = 0; 53 unsigned int n; 54 struct stat st; 55 int fd; 56 57 /* If we've already looked this up, we're done. */ 58 if (_searched) 59 return; 60 _searched = 1; 61 62 /* Create a file on disk in the current default dir. */ 63 fd = open("test_gid", O_CREAT | O_BINARY, 0664); 64 failure("Couldn't create a file for gid testing."); 65 assert(fd > 0); 66 67 /* See what GID it ended up with. This is our "valid" GID. */ 68 assert(fstat(fd, &st) == 0); 69 _default_gid = st.st_gid; 70 71 /* Find a GID for which fchown() fails. This is our "invalid" GID. */ 72 _invalid_gid = -1; 73 /* This loop stops when we wrap the gid or examine 10,000 gids. */ 74 for (gid = 1, n = 1; gid == n && n < 10000 ; n++, gid++) { 75 if (fchown(fd, uid, gid) != 0) { 76 _invalid_gid = gid; 77 break; 78 } 79 } 80 81 /* 82 * Find a GID for which fchown() succeeds, but which isn't the 83 * default. This is the "alternate" gid. 84 */ 85 _alt_gid = -1; 86 for (gid = 0, n = 0; gid == n && n < 10000 ; n++, gid++) { 87 /* _alt_gid must be different than _default_gid */ 88 if (gid == (gid_t)_default_gid) 89 continue; 90 if (fchown(fd, uid, gid) == 0) { 91 _alt_gid = gid; 92 break; 93 } 94 } 95 close(fd); 96} 97 98static int 99altgid(void) 100{ 101 searchgid(); 102 return (_alt_gid); 103} 104 105static int 106invalidgid(void) 107{ 108 searchgid(); 109 return (_invalid_gid); 110} 111 112static int 113defaultgid(void) 114{ 115 searchgid(); 116 return (_default_gid); 117} 118#endif 119 120/* 121 * Exercise permission and ownership restores. 122 * In particular, try to exercise a bunch of border cases related 123 * to files/dirs that already exist, SUID/SGID bits, etc. 124 */ 125 126DEFINE_TEST(test_write_disk_perms) 127{ 128#if defined(_WIN32) && !defined(__CYGWIN__) 129 skipping("archive_write_disk interface"); 130#else 131 struct archive *a; 132 struct archive_entry *ae; 133 struct stat st; 134 135 assertUmask(UMASK); 136 137 /* 138 * Set ownership of the current directory to the group of this 139 * process. Otherwise, the SGID tests below fail if the 140 * /tmp directory is owned by a group to which we don't belong 141 * and we're on a system where group ownership is inherited. 142 * (Because we're not allowed to SGID files with defaultgid().) 143 */ 144 assertEqualInt(0, chown(".", getuid(), getgid())); 145 146 /* Create an archive_write_disk object. */ 147 assert((a = archive_write_disk_new()) != NULL); 148 149 /* Write a regular file to it. */ 150 assert((ae = archive_entry_new()) != NULL); 151 archive_entry_copy_pathname(ae, "file_0755"); 152 archive_entry_set_mode(ae, S_IFREG | 0777); 153 assert(0 == archive_write_header(a, ae)); 154 assert(0 == archive_write_finish_entry(a)); 155 archive_entry_free(ae); 156 157 /* Write a regular file, then write over it. */ 158 /* For files, the perms should get updated. */ 159 assert((ae = archive_entry_new()) != NULL); 160 archive_entry_copy_pathname(ae, "file_overwrite_0144"); 161 archive_entry_set_mode(ae, S_IFREG | 0777); 162 assert(0 == archive_write_header(a, ae)); 163 archive_entry_free(ae); 164 assert(0 == archive_write_finish_entry(a)); 165 /* Check that file was created with different perms. */ 166 assert(0 == stat("file_overwrite_0144", &st)); 167 failure("file_overwrite_0144: st.st_mode=%o", st.st_mode); 168 assert((st.st_mode & 07777) != 0144); 169 /* Overwrite, this should change the perms. */ 170 assert((ae = archive_entry_new()) != NULL); 171 archive_entry_copy_pathname(ae, "file_overwrite_0144"); 172 archive_entry_set_mode(ae, S_IFREG | 0144); 173 assert(0 == archive_write_header(a, ae)); 174 archive_entry_free(ae); 175 assert(0 == archive_write_finish_entry(a)); 176 177 /* Write a regular dir. */ 178 assert((ae = archive_entry_new()) != NULL); 179 archive_entry_copy_pathname(ae, "dir_0514"); 180 archive_entry_set_mode(ae, S_IFDIR | 0514); 181 assert(0 == archive_write_header(a, ae)); 182 archive_entry_free(ae); 183 assert(0 == archive_write_finish_entry(a)); 184 185 /* Overwrite an existing dir. */ 186 /* For dir, the first perms should get left. */ 187 assertMakeDir("dir_overwrite_0744", 0744); 188 /* Check original perms. */ 189 assert(0 == stat("dir_overwrite_0744", &st)); 190 failure("dir_overwrite_0744: st.st_mode=%o", st.st_mode); 191 assert((st.st_mode & 0777) == 0744); 192 /* Overwrite shouldn't edit perms. */ 193 assert((ae = archive_entry_new()) != NULL); 194 archive_entry_copy_pathname(ae, "dir_overwrite_0744"); 195 archive_entry_set_mode(ae, S_IFDIR | 0777); 196 assert(0 == archive_write_header(a, ae)); 197 archive_entry_free(ae); 198 assert(0 == archive_write_finish_entry(a)); 199 /* Make sure they're unchanged. */ 200 assert(0 == stat("dir_overwrite_0744", &st)); 201 failure("dir_overwrite_0744: st.st_mode=%o", st.st_mode); 202 assert((st.st_mode & 0777) == 0744); 203 204 /* Write a regular file with SUID bit, but don't use _EXTRACT_PERM. */ 205 assert((ae = archive_entry_new()) != NULL); 206 archive_entry_copy_pathname(ae, "file_no_suid"); 207 archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0777); 208 archive_write_disk_set_options(a, 0); 209 assert(0 == archive_write_header(a, ae)); 210 assert(0 == archive_write_finish_entry(a)); 211 212 /* Write a regular file with ARCHIVE_EXTRACT_PERM. */ 213 assert(archive_entry_clear(ae) != NULL); 214 archive_entry_copy_pathname(ae, "file_0777"); 215 archive_entry_set_mode(ae, S_IFREG | 0777); 216 archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM); 217 assert(0 == archive_write_header(a, ae)); 218 assert(0 == archive_write_finish_entry(a)); 219 220 /* Write a regular file with ARCHIVE_EXTRACT_PERM & SUID bit */ 221 assert(archive_entry_clear(ae) != NULL); 222 archive_entry_copy_pathname(ae, "file_4742"); 223 archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0742); 224 archive_entry_set_uid(ae, getuid()); 225 archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM); 226 assert(0 == archive_write_header(a, ae)); 227 assert(0 == archive_write_finish_entry(a)); 228 229 /* 230 * Write a regular file with ARCHIVE_EXTRACT_PERM & SUID bit, 231 * but wrong uid. POSIX says you shouldn't restore SUID bit 232 * unless the UID could be restored. 233 */ 234 assert(archive_entry_clear(ae) != NULL); 235 archive_entry_copy_pathname(ae, "file_bad_suid"); 236 archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0742); 237 archive_entry_set_uid(ae, getuid() + 1); 238 archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM); 239 assertA(0 == archive_write_header(a, ae)); 240 /* 241 * Because we didn't ask for owner, the failure to 242 * restore SUID shouldn't return a failure. 243 * We check below to make sure SUID really wasn't set. 244 * See more detailed comments below. 245 */ 246 failure("Opportunistic SUID failure shouldn't return error."); 247 assertEqualInt(0, archive_write_finish_entry(a)); 248 249 if (getuid() != 0) { 250 assert(archive_entry_clear(ae) != NULL); 251 archive_entry_copy_pathname(ae, "file_bad_suid2"); 252 archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0742); 253 archive_entry_set_uid(ae, getuid() + 1); 254 archive_write_disk_set_options(a, 255 ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_OWNER); 256 assertA(0 == archive_write_header(a, ae)); 257 /* Owner change should fail here. */ 258 failure("Non-opportunistic SUID failure should return error."); 259 assertEqualInt(ARCHIVE_WARN, archive_write_finish_entry(a)); 260 } 261 262 /* Write a regular file with ARCHIVE_EXTRACT_PERM & SGID bit */ 263 assert(archive_entry_clear(ae) != NULL); 264 archive_entry_copy_pathname(ae, "file_perm_sgid"); 265 archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742); 266 archive_entry_set_gid(ae, defaultgid()); 267 archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM); 268 assert(0 == archive_write_header(a, ae)); 269 failure("Setting SGID bit should succeed here."); 270 assertEqualIntA(a, 0, archive_write_finish_entry(a)); 271 272 if (altgid() == -1) { 273 /* 274 * Current user must belong to at least two groups or 275 * else we can't test setting the GID to another group. 276 */ 277 skipping("Current user can't test gid restore: must belong to more than one group."); 278 } else { 279 /* 280 * Write a regular file with ARCHIVE_EXTRACT_PERM & SGID bit 281 * but without ARCHIVE_EXTRACT_OWNER. 282 */ 283 /* 284 * This is a weird case: The user has asked for permissions to 285 * be restored but not asked for ownership to be restored. As 286 * a result, the default file creation will create a file with 287 * the wrong group. There are several possible behaviors for 288 * libarchive in this scenario: 289 * = Set the SGID bit. It is wrong and a security hole to 290 * set SGID with the wrong group. Even POSIX thinks so. 291 * = Implicitly set the group. I don't like this. 292 * = drop the SGID bit and warn (the old libarchive behavior) 293 * = drop the SGID bit and don't warn (the current libarchive 294 * behavior). 295 * The current behavior sees SGID/SUID restore when you 296 * don't ask for owner restore as an "opportunistic" 297 * action. That is, libarchive should do it if it can, 298 * but if it can't, it's not an error. 299 */ 300 assert(archive_entry_clear(ae) != NULL); 301 archive_entry_copy_pathname(ae, "file_alt_sgid"); 302 archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742); 303 archive_entry_set_uid(ae, getuid()); 304 archive_entry_set_gid(ae, altgid()); 305 archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM); 306 assert(0 == archive_write_header(a, ae)); 307 failure("Setting SGID bit should fail because of group mismatch but the failure should be silent because we didn't ask for the group to be set."); 308 assertEqualIntA(a, 0, archive_write_finish_entry(a)); 309 310 /* 311 * As above, but add _EXTRACT_OWNER to verify that it 312 * does succeed. 313 */ 314 assert(archive_entry_clear(ae) != NULL); 315 archive_entry_copy_pathname(ae, "file_alt_sgid_owner"); 316 archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742); 317 archive_entry_set_uid(ae, getuid()); 318 archive_entry_set_gid(ae, altgid()); 319 archive_write_disk_set_options(a, 320 ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_OWNER); 321 assert(0 == archive_write_header(a, ae)); 322 failure("Setting SGID bit should succeed here."); 323 assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a)); 324 } 325 326 /* 327 * Write a regular file with ARCHIVE_EXTRACT_PERM & SGID bit, 328 * but wrong GID. POSIX says you shouldn't restore SGID bit 329 * unless the GID could be restored. 330 */ 331 if (invalidgid() == -1) { 332 /* This test always fails for root. */ 333 printf("Running as root: Can't test SGID failures.\n"); 334 } else { 335 assert(archive_entry_clear(ae) != NULL); 336 archive_entry_copy_pathname(ae, "file_bad_sgid"); 337 archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742); 338 archive_entry_set_gid(ae, invalidgid()); 339 archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM); 340 assertA(0 == archive_write_header(a, ae)); 341 failure("This SGID restore should fail without an error."); 342 assertEqualIntA(a, 0, archive_write_finish_entry(a)); 343 344 assert(archive_entry_clear(ae) != NULL); 345 archive_entry_copy_pathname(ae, "file_bad_sgid2"); 346 archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742); 347 archive_entry_set_gid(ae, invalidgid()); 348 archive_write_disk_set_options(a, 349 ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_OWNER); 350 assertA(0 == archive_write_header(a, ae)); 351 failure("This SGID restore should fail with an error."); 352 assertEqualIntA(a, ARCHIVE_WARN, archive_write_finish_entry(a)); 353 } 354 355 /* Set ownership should fail if we're not root. */ 356 if (getuid() == 0) { 357 printf("Running as root: Can't test setuid failures.\n"); 358 } else { 359 assert(archive_entry_clear(ae) != NULL); 360 archive_entry_copy_pathname(ae, "file_bad_owner"); 361 archive_entry_set_mode(ae, S_IFREG | 0744); 362 archive_entry_set_uid(ae, getuid() + 1); 363 archive_write_disk_set_options(a, ARCHIVE_EXTRACT_OWNER); 364 assertA(0 == archive_write_header(a, ae)); 365 assertEqualIntA(a,ARCHIVE_WARN,archive_write_finish_entry(a)); 366 } 367 368 assertEqualInt(ARCHIVE_OK, archive_write_free(a)); 369 archive_entry_free(ae); 370 371 /* Test the entries on disk. */ 372 assert(0 == stat("file_0755", &st)); 373 failure("file_0755: st.st_mode=%o", st.st_mode); 374 assert((st.st_mode & 07777) == 0755); 375 376 assert(0 == stat("file_overwrite_0144", &st)); 377 failure("file_overwrite_0144: st.st_mode=%o", st.st_mode); 378 assert((st.st_mode & 07777) == 0144); 379 380 assert(0 == stat("dir_0514", &st)); 381 failure("dir_0514: st.st_mode=%o", st.st_mode); 382 assert((st.st_mode & 07777) == 0514); 383 384 assert(0 == stat("dir_overwrite_0744", &st)); 385 failure("dir_overwrite_0744: st.st_mode=%o", st.st_mode); 386 assert((st.st_mode & 0777) == 0744); 387 388 assert(0 == stat("file_no_suid", &st)); 389 failure("file_0755: st.st_mode=%o", st.st_mode); 390 assert((st.st_mode & 07777) == 0755); 391 392 assert(0 == stat("file_0777", &st)); 393 failure("file_0777: st.st_mode=%o", st.st_mode); 394 assert((st.st_mode & 07777) == 0777); 395 396 /* SUID bit should get set here. */ 397 assert(0 == stat("file_4742", &st)); 398 failure("file_4742: st.st_mode=%o", st.st_mode); 399 assert((st.st_mode & 07777) == (S_ISUID | 0742)); 400 401 /* SUID bit should NOT have been set here. */ 402 assert(0 == stat("file_bad_suid", &st)); 403 failure("file_bad_suid: st.st_mode=%o", st.st_mode); 404 assert((st.st_mode & 07777) == (0742)); 405 406 /* Some things don't fail if you're root, so suppress this. */ 407 if (getuid() != 0) { 408 /* SUID bit should NOT have been set here. */ 409 assert(0 == stat("file_bad_suid2", &st)); 410 failure("file_bad_suid2: st.st_mode=%o", st.st_mode); 411 assert((st.st_mode & 07777) == (0742)); 412 } 413 414 /* SGID should be set here. */ 415 assert(0 == stat("file_perm_sgid", &st)); 416 failure("file_perm_sgid: st.st_mode=%o", st.st_mode); 417 assert((st.st_mode & 07777) == (S_ISGID | 0742)); 418 419 if (altgid() != -1) { 420 /* SGID should not be set here. */ 421 assert(0 == stat("file_alt_sgid", &st)); 422 failure("file_alt_sgid: st.st_mode=%o", st.st_mode); 423 assert((st.st_mode & 07777) == (0742)); 424 425 /* SGID should be set here. */ 426 assert(0 == stat("file_alt_sgid_owner", &st)); 427 failure("file_alt_sgid: st.st_mode=%o", st.st_mode); 428 assert((st.st_mode & 07777) == (S_ISGID | 0742)); 429 } 430 431 if (invalidgid() != -1) { 432 /* SGID should NOT be set here. */ 433 assert(0 == stat("file_bad_sgid", &st)); 434 failure("file_bad_sgid: st.st_mode=%o", st.st_mode); 435 assert((st.st_mode & 07777) == (0742)); 436 /* SGID should NOT be set here. */ 437 assert(0 == stat("file_bad_sgid2", &st)); 438 failure("file_bad_sgid2: st.st_mode=%o", st.st_mode); 439 assert((st.st_mode & 07777) == (0742)); 440 } 441 442 if (getuid() != 0) { 443 assert(0 == stat("file_bad_owner", &st)); 444 failure("file_bad_owner: st.st_mode=%o", st.st_mode); 445 assert((st.st_mode & 07777) == (0744)); 446 failure("file_bad_owner: st.st_uid=%d getuid()=%d", 447 st.st_uid, getuid()); 448 /* The entry had getuid()+1, but because we're 449 * not root, we should not have been able to set that. */ 450 assert(st.st_uid == getuid()); 451 } 452#endif 453} 454