summaryrefslogtreecommitdiff
path: root/archivers/libarchive/files/libarchive/test/test_write_disk_perms.c
blob: 8205d6abc59a36e3b7a10def7172be5ef1b24711 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
/*-
 * Copyright (c) 2003-2007 Tim Kientzle
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include "test.h"
__FBSDID("$FreeBSD: head/lib/libarchive/test/test_write_disk_perms.c 201247 2009-12-30 05:59:21Z kientzle $");

#if !defined(_WIN32) || defined(__CYGWIN__)

#define UMASK 022

static long _default_gid = -1;
static long _invalid_gid = -1;
static long _alt_gid = -1;

/*
 * To fully test SGID restores, we need three distinct GIDs to work
 * with:
 *    * the GID that files are created with by default (for the
 *      current user in the current directory)
 *    * An "alt gid" that this user can create files with
 *    * An "invalid gid" that this user is not permitted to create
 *      files with.
 * The second fails if this user doesn't belong to at least two groups;
 * the third fails if the current user is root.
 */
static void
searchgid(void)
{
	static int   _searched = 0;
	uid_t uid = getuid();
	gid_t gid = 0;
	unsigned int n;
	struct stat st;
	int fd;

	/* If we've already looked this up, we're done. */
	if (_searched)
		return;
	_searched = 1;

	/* Create a file on disk in the current default dir. */
	fd = open("test_gid", O_CREAT | O_BINARY, 0664);
	failure("Couldn't create a file for gid testing.");
	assert(fd > 0);

	/* See what GID it ended up with.  This is our "valid" GID. */
	assert(fstat(fd, &st) == 0);
	_default_gid = st.st_gid;

	/* Find a GID for which fchown() fails.  This is our "invalid" GID. */
	_invalid_gid = -1;
	/* This loop stops when we wrap the gid or examine 10,000 gids. */
	for (gid = 1, n = 1; gid == n && n < 10000 ; n++, gid++) {
		if (fchown(fd, uid, gid) != 0) {
			_invalid_gid = gid;
			break;
		}
	}

	/*
	 * Find a GID for which fchown() succeeds, but which isn't the
	 * default.  This is the "alternate" gid.
	 */
	_alt_gid = -1;
	for (gid = 0, n = 0; gid == n && n < 10000 ; n++, gid++) {
		/* _alt_gid must be different than _default_gid */
		if (gid == (gid_t)_default_gid)
			continue;
		if (fchown(fd, uid, gid) == 0) {
			_alt_gid = gid;
			break;
		}
	}
	close(fd);
}

static int
altgid(void)
{
	searchgid();
	return (_alt_gid);
}

static int
invalidgid(void)
{
	searchgid();
	return (_invalid_gid);
}

static int
defaultgid(void)
{
	searchgid();
	return (_default_gid);
}
#endif

/*
 * Exercise permission and ownership restores.
 * In particular, try to exercise a bunch of border cases related
 * to files/dirs that already exist, SUID/SGID bits, etc.
 */

DEFINE_TEST(test_write_disk_perms)
{
#if defined(_WIN32) && !defined(__CYGWIN__)
	skipping("archive_write_disk interface");
#else
	struct archive *a;
	struct archive_entry *ae;
	struct stat st;
	uid_t original_uid;
	uid_t try_to_change_uid;

	assertUmask(UMASK);

	/*
	 * Set ownership of the current directory to the group of this
	 * process.  Otherwise, the SGID tests below fail if the
	 * /tmp directory is owned by a group to which we don't belong
	 * and we're on a system where group ownership is inherited.
	 * (Because we're not allowed to SGID files with defaultgid().)
	 */
	assertEqualInt(0, chown(".", getuid(), getgid()));

	/* Create an archive_write_disk object. */
	assert((a = archive_write_disk_new()) != NULL);

	/* Write a regular file to it. */
	assert((ae = archive_entry_new()) != NULL);
	archive_entry_copy_pathname(ae, "file_0755");
	archive_entry_set_mode(ae, S_IFREG | 0777);
	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
	archive_entry_free(ae);

	/* Write a regular file, then write over it. */
	/* For files, the perms should get updated. */
	assert((ae = archive_entry_new()) != NULL);
	archive_entry_copy_pathname(ae, "file_overwrite_0144");
	archive_entry_set_mode(ae, S_IFREG | 0777);
	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
	archive_entry_free(ae);
	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
	/* Check that file was created with different perms. */
	assertEqualInt(0, stat("file_overwrite_0144", &st));
	failure("file_overwrite_0144: st.st_mode=%o", st.st_mode);
	assert((st.st_mode & 07777) != 0144);
	/* Overwrite, this should change the perms. */
	assert((ae = archive_entry_new()) != NULL);
	archive_entry_copy_pathname(ae, "file_overwrite_0144");
	archive_entry_set_mode(ae, S_IFREG | 0144);
	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
	archive_entry_free(ae);
	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));

	/* Write a regular dir. */
	assert((ae = archive_entry_new()) != NULL);
	archive_entry_copy_pathname(ae, "dir_0514");
	archive_entry_set_mode(ae, S_IFDIR | 0514);
	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
	archive_entry_free(ae);
	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));

	/* Overwrite an existing dir. */
	/* For dir, the first perms should get left. */
	assertMakeDir("dir_overwrite_0744", 0744);
	/* Check original perms. */
	assertEqualInt(0, stat("dir_overwrite_0744", &st));
	failure("dir_overwrite_0744: st.st_mode=%o", st.st_mode);
	assertEqualInt(st.st_mode & 0777, 0744);
	/* Overwrite shouldn't edit perms. */
	assert((ae = archive_entry_new()) != NULL);
	archive_entry_copy_pathname(ae, "dir_overwrite_0744");
	archive_entry_set_mode(ae, S_IFDIR | 0777);
	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
	archive_entry_free(ae);
	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
	/* Make sure they're unchanged. */
	assertEqualInt(0, stat("dir_overwrite_0744", &st));
	failure("dir_overwrite_0744: st.st_mode=%o", st.st_mode);
	assertEqualInt(st.st_mode & 0777, 0744);

	/* For dir, the owner should get left when not overwritting. */
	assertMakeDir("dir_owner", 0744);

	if (getuid() == 0) {
		original_uid = getuid() + 1;
		try_to_change_uid = getuid();
		assertEqualInt(0, chown("dir_owner", original_uid, getgid()));
	} else {
		original_uid = getuid();
		try_to_change_uid = getuid() + 1;
	}

	/* Check original owner. */
	assertEqualInt(0, stat("dir_owner", &st));
	failure("dir_owner: st.st_uid=%d", st.st_uid);
	assertEqualInt(st.st_uid, original_uid);
	/* Shouldn't try to edit the owner when no overwrite option is set. */
	assert((ae = archive_entry_new()) != NULL);
	archive_entry_copy_pathname(ae, "dir_owner");
	archive_entry_set_mode(ae, S_IFDIR | 0744);
	archive_entry_set_uid(ae, try_to_change_uid);
	archive_write_disk_set_options(a,
	    ARCHIVE_EXTRACT_OWNER | ARCHIVE_EXTRACT_NO_OVERWRITE);
	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
	archive_entry_free(ae);
	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
	/* Make sure they're unchanged. */
	assertEqualInt(0, stat("dir_owner", &st));
	failure("dir_owner: st.st_uid=%d", st.st_uid);
	assertEqualInt(st.st_uid, original_uid);

	/* Write a regular file with SUID bit, but don't use _EXTRACT_PERM. */
	assert((ae = archive_entry_new()) != NULL);
	archive_entry_copy_pathname(ae, "file_no_suid");
	archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0777);
	archive_write_disk_set_options(a, 0);
	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));

	/* Write a regular file with ARCHIVE_EXTRACT_PERM. */
	assert(archive_entry_clear(ae) != NULL);
	archive_entry_copy_pathname(ae, "file_0777");
	archive_entry_set_mode(ae, S_IFREG | 0777);
	archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM);
	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));

	/* Write a regular file with ARCHIVE_EXTRACT_PERM & SUID bit */
	assert(archive_entry_clear(ae) != NULL);
	archive_entry_copy_pathname(ae, "file_4742");
	archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0742);
	archive_entry_set_uid(ae, getuid());
	archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM);
	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));

	/*
	 * Write a regular file with ARCHIVE_EXTRACT_PERM & SUID bit,
	 * but wrong uid.  POSIX says you shouldn't restore SUID bit
	 * unless the UID could be restored.
	 */
	assert(archive_entry_clear(ae) != NULL);
	archive_entry_copy_pathname(ae, "file_bad_suid");
	archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0742);
	archive_entry_set_uid(ae, getuid() + 1);
	archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM);
	assertA(0 == archive_write_header(a, ae));
	/*
	 * Because we didn't ask for owner, the failure to
	 * restore SUID shouldn't return a failure.
	 * We check below to make sure SUID really wasn't set.
	 * See more detailed comments below.
	 */
	failure("Opportunistic SUID failure shouldn't return error.");
	assertEqualInt(0, archive_write_finish_entry(a));

        if (getuid() != 0) {
		assert(archive_entry_clear(ae) != NULL);
		archive_entry_copy_pathname(ae, "file_bad_suid2");
		archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0742);
		archive_entry_set_uid(ae, getuid() + 1);
		archive_write_disk_set_options(a,
		    ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_OWNER);
		assertA(0 == archive_write_header(a, ae));
		/* Owner change should fail here. */
		failure("Non-opportunistic SUID failure should return error.");
		assertEqualInt(ARCHIVE_WARN, archive_write_finish_entry(a));
	}

	/* Write a regular file with ARCHIVE_EXTRACT_PERM & SGID bit */
	assert(archive_entry_clear(ae) != NULL);
	archive_entry_copy_pathname(ae, "file_perm_sgid");
	archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742);
	archive_entry_set_gid(ae, defaultgid());
	archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM);
	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
	failure("Setting SGID bit should succeed here.");
	assertEqualIntA(a, 0, archive_write_finish_entry(a));

	if (altgid() == -1) {
		/*
		 * Current user must belong to at least two groups or
		 * else we can't test setting the GID to another group.
		 */
		skipping("Current user can't test gid restore: must belong to more than one group.");
	} else {
		/*
		 * Write a regular file with ARCHIVE_EXTRACT_PERM & SGID bit
		 * but without ARCHIVE_EXTRACT_OWNER.
		 */
		/*
		 * This is a weird case: The user has asked for permissions to
		 * be restored but not asked for ownership to be restored.  As
		 * a result, the default file creation will create a file with
		 * the wrong group.  There are several possible behaviors for
		 * libarchive in this scenario:
		 *  = Set the SGID bit.  It is wrong and a security hole to
		 *    set SGID with the wrong group.  Even POSIX thinks so.
		 *  = Implicitly set the group.  I don't like this.
		 *  = drop the SGID bit and warn (the old libarchive behavior)
		 *  = drop the SGID bit and don't warn (the current libarchive
		 *    behavior).
		 * The current behavior sees SGID/SUID restore when you
		 * don't ask for owner restore as an "opportunistic"
		 * action.  That is, libarchive should do it if it can,
		 * but if it can't, it's not an error.
		 */
		assert(archive_entry_clear(ae) != NULL);
		archive_entry_copy_pathname(ae, "file_alt_sgid");
		archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742);
		archive_entry_set_uid(ae, getuid());
		archive_entry_set_gid(ae, altgid());
		archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM);
		assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
		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.");
		assertEqualIntA(a, 0, archive_write_finish_entry(a));

		/*
		 * As above, but add _EXTRACT_OWNER to verify that it
		 * does succeed.
		 */
		assert(archive_entry_clear(ae) != NULL);
		archive_entry_copy_pathname(ae, "file_alt_sgid_owner");
		archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742);
		archive_entry_set_uid(ae, getuid());
		archive_entry_set_gid(ae, altgid());
		archive_write_disk_set_options(a,
		    ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_OWNER);
		assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
		failure("Setting SGID bit should succeed here.");
		assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
	}

	/*
	 * Write a regular file with ARCHIVE_EXTRACT_PERM & SGID bit,
	 * but wrong GID.  POSIX says you shouldn't restore SGID bit
	 * unless the GID could be restored.
	 */
	if (invalidgid() == -1) {
		/* This test always fails for root. */
		printf("Running as root: Can't test SGID failures.\n");
	} else {
		assert(archive_entry_clear(ae) != NULL);
		archive_entry_copy_pathname(ae, "file_bad_sgid");
		archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742);
		archive_entry_set_gid(ae, invalidgid());
		archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM);
		assertA(0 == archive_write_header(a, ae));
		failure("This SGID restore should fail without an error.");
		assertEqualIntA(a, 0, archive_write_finish_entry(a));

		assert(archive_entry_clear(ae) != NULL);
		archive_entry_copy_pathname(ae, "file_bad_sgid2");
		archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742);
		archive_entry_set_gid(ae, invalidgid());
		archive_write_disk_set_options(a,
		    ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_OWNER);
		assertA(0 == archive_write_header(a, ae));
		failure("This SGID restore should fail with an error.");
		assertEqualIntA(a, ARCHIVE_WARN, archive_write_finish_entry(a));
	}

	/* Set ownership should fail if we're not root. */
	if (getuid() == 0) {
		printf("Running as root: Can't test setuid failures.\n");
	} else {
		assert(archive_entry_clear(ae) != NULL);
		archive_entry_copy_pathname(ae, "file_bad_owner");
		archive_entry_set_mode(ae, S_IFREG | 0744);
		archive_entry_set_uid(ae, getuid() + 1);
		archive_write_disk_set_options(a, ARCHIVE_EXTRACT_OWNER);
		assertA(0 == archive_write_header(a, ae));
		assertEqualIntA(a,ARCHIVE_WARN,archive_write_finish_entry(a));
	}

	assertEqualInt(ARCHIVE_OK, archive_write_free(a));
	archive_entry_free(ae);

	/* Test the entries on disk. */
	assertEqualInt(0, stat("file_0755", &st));
	failure("file_0755: st.st_mode=%o", st.st_mode);
	assertEqualInt(st.st_mode & 07777, 0755);

	assertEqualInt(0, stat("file_overwrite_0144", &st));
	failure("file_overwrite_0144: st.st_mode=%o", st.st_mode);
	assertEqualInt(st.st_mode & 07777, 0144);

	assertEqualInt(0, stat("dir_0514", &st));
	failure("dir_0514: st.st_mode=%o", st.st_mode);
	assertEqualInt(st.st_mode & 07777, 0514);

	assertEqualInt(0, stat("dir_overwrite_0744", &st));
	failure("dir_overwrite_0744: st.st_mode=%o", st.st_mode);
	assertEqualInt(st.st_mode & 0777, 0744);

	assertEqualInt(0, stat("file_no_suid", &st));
	failure("file_0755: st.st_mode=%o", st.st_mode);
	assertEqualInt(st.st_mode & 07777, 0755);

	assertEqualInt(0, stat("file_0777", &st));
	failure("file_0777: st.st_mode=%o", st.st_mode);
	assertEqualInt(st.st_mode & 07777, 0777);

	/* SUID bit should get set here. */
	assertEqualInt(0, stat("file_4742", &st));
	failure("file_4742: st.st_mode=%o", st.st_mode);
	assertEqualInt(st.st_mode & 07777, S_ISUID | 0742);

	/* SUID bit should NOT have been set here. */
	assertEqualInt(0, stat("file_bad_suid", &st));
	failure("file_bad_suid: st.st_mode=%o", st.st_mode);
	assertEqualInt(st.st_mode & 07777, 0742);

	/* Some things don't fail if you're root, so suppress this. */
	if (getuid() != 0) {
		/* SUID bit should NOT have been set here. */
		assertEqualInt(0, stat("file_bad_suid2", &st));
		failure("file_bad_suid2: st.st_mode=%o", st.st_mode);
		assertEqualInt(st.st_mode & 07777, 0742);
	}

	/* SGID should be set here. */
	assertEqualInt(0, stat("file_perm_sgid", &st));
	failure("file_perm_sgid: st.st_mode=%o", st.st_mode);
	assertEqualInt(st.st_mode & 07777, S_ISGID | 0742);

	if (altgid() != -1) {
		/* SGID should not be set here. */
		assertEqualInt(0, stat("file_alt_sgid", &st));
		failure("file_alt_sgid: st.st_mode=%o", st.st_mode);
		assertEqualInt(st.st_mode & 07777, 0742);

		/* SGID should be set here. */
		assertEqualInt(0, stat("file_alt_sgid_owner", &st));
		failure("file_alt_sgid: st.st_mode=%o", st.st_mode);
		assertEqualInt(st.st_mode & 07777, S_ISGID | 0742);
	}

	if (invalidgid() != -1) {
		/* SGID should NOT be set here. */
		assertEqualInt(0, stat("file_bad_sgid", &st));
		failure("file_bad_sgid: st.st_mode=%o", st.st_mode);
		assertEqualInt(st.st_mode & 07777, 0742);
		/* SGID should NOT be set here. */
		assertEqualInt(0, stat("file_bad_sgid2", &st));
		failure("file_bad_sgid2: st.st_mode=%o", st.st_mode);
		assertEqualInt(st.st_mode & 07777, 0742);
	}

	if (getuid() != 0) {
		assertEqualInt(0, stat("file_bad_owner", &st));
		failure("file_bad_owner: st.st_mode=%o", st.st_mode);
		assertEqualInt(st.st_mode & 07777, 0744);
		failure("file_bad_owner: st.st_uid=%d getuid()=%d",
		    st.st_uid, getuid());
		/* The entry had getuid()+1, but because we're
		 * not root, we should not have been able to set that. */
		assertEqualInt(st.st_uid, getuid());
	}
#endif
}