test_write_disk_perms.c revision 328827
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: stable/11/contrib/libarchive/libarchive/test/test_write_disk_perms.c 328827 2018-02-03 02:17:04Z 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	uid_t original_uid;
135	uid_t try_to_change_uid;
136
137	assertUmask(UMASK);
138
139	/*
140	 * Set ownership of the current directory to the group of this
141	 * process.  Otherwise, the SGID tests below fail if the
142	 * /tmp directory is owned by a group to which we don't belong
143	 * and we're on a system where group ownership is inherited.
144	 * (Because we're not allowed to SGID files with defaultgid().)
145	 */
146	assertEqualInt(0, chown(".", getuid(), getgid()));
147
148	/* Create an archive_write_disk object. */
149	assert((a = archive_write_disk_new()) != NULL);
150
151	/* Write a regular file to it. */
152	assert((ae = archive_entry_new()) != NULL);
153	archive_entry_copy_pathname(ae, "file_0755");
154	archive_entry_set_mode(ae, S_IFREG | 0777);
155	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
156	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
157	archive_entry_free(ae);
158
159	/* Write a regular file, then write over it. */
160	/* For files, the perms should get updated. */
161	assert((ae = archive_entry_new()) != NULL);
162	archive_entry_copy_pathname(ae, "file_overwrite_0144");
163	archive_entry_set_mode(ae, S_IFREG | 0777);
164	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
165	archive_entry_free(ae);
166	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
167	/* Check that file was created with different perms. */
168	assertEqualInt(0, stat("file_overwrite_0144", &st));
169	failure("file_overwrite_0144: st.st_mode=%o", st.st_mode);
170	assert((st.st_mode & 07777) != 0144);
171	/* Overwrite, this should change the perms. */
172	assert((ae = archive_entry_new()) != NULL);
173	archive_entry_copy_pathname(ae, "file_overwrite_0144");
174	archive_entry_set_mode(ae, S_IFREG | 0144);
175	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
176	archive_entry_free(ae);
177	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
178
179	/* Write a regular dir. */
180	assert((ae = archive_entry_new()) != NULL);
181	archive_entry_copy_pathname(ae, "dir_0514");
182	archive_entry_set_mode(ae, S_IFDIR | 0514);
183	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
184	archive_entry_free(ae);
185	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
186
187	/* Overwrite an existing dir. */
188	/* For dir, the first perms should get left. */
189	assertMakeDir("dir_overwrite_0744", 0744);
190	/* Check original perms. */
191	assertEqualInt(0, stat("dir_overwrite_0744", &st));
192	failure("dir_overwrite_0744: st.st_mode=%o", st.st_mode);
193	assertEqualInt(st.st_mode & 0777, 0744);
194	/* Overwrite shouldn't edit perms. */
195	assert((ae = archive_entry_new()) != NULL);
196	archive_entry_copy_pathname(ae, "dir_overwrite_0744");
197	archive_entry_set_mode(ae, S_IFDIR | 0777);
198	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
199	archive_entry_free(ae);
200	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
201	/* Make sure they're unchanged. */
202	assertEqualInt(0, stat("dir_overwrite_0744", &st));
203	failure("dir_overwrite_0744: st.st_mode=%o", st.st_mode);
204	assertEqualInt(st.st_mode & 0777, 0744);
205
206	/* For dir, the owner should get left when not overwritting. */
207	assertMakeDir("dir_owner", 0744);
208
209	if (getuid() == 0) {
210		original_uid = getuid() + 1;
211		try_to_change_uid = getuid();
212		assertEqualInt(0, chown("dir_owner", original_uid, getgid()));
213	} else {
214		original_uid = getuid();
215		try_to_change_uid = getuid() + 1;
216	}
217
218	/* Check original owner. */
219	assertEqualInt(0, stat("dir_owner", &st));
220	failure("dir_owner: st.st_uid=%d", st.st_uid);
221	assertEqualInt(st.st_uid, original_uid);
222	/* Shouldn't try to edit the owner when no overwrite option is set. */
223	assert((ae = archive_entry_new()) != NULL);
224	archive_entry_copy_pathname(ae, "dir_owner");
225	archive_entry_set_mode(ae, S_IFDIR | 0744);
226	archive_entry_set_uid(ae, try_to_change_uid);
227	archive_write_disk_set_options(a,
228	    ARCHIVE_EXTRACT_OWNER | ARCHIVE_EXTRACT_NO_OVERWRITE);
229	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
230	archive_entry_free(ae);
231	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
232	/* Make sure they're unchanged. */
233	assertEqualInt(0, stat("dir_owner", &st));
234	failure("dir_owner: st.st_uid=%d", st.st_uid);
235	assertEqualInt(st.st_uid, original_uid);
236
237	/* Write a regular file with SUID bit, but don't use _EXTRACT_PERM. */
238	assert((ae = archive_entry_new()) != NULL);
239	archive_entry_copy_pathname(ae, "file_no_suid");
240	archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0777);
241	archive_write_disk_set_options(a, 0);
242	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
243	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
244
245	/* Write a regular file with ARCHIVE_EXTRACT_PERM. */
246	assert(archive_entry_clear(ae) != NULL);
247	archive_entry_copy_pathname(ae, "file_0777");
248	archive_entry_set_mode(ae, S_IFREG | 0777);
249	archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM);
250	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
251	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
252
253	/* Write a regular file with ARCHIVE_EXTRACT_PERM & SUID bit */
254	assert(archive_entry_clear(ae) != NULL);
255	archive_entry_copy_pathname(ae, "file_4742");
256	archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0742);
257	archive_entry_set_uid(ae, getuid());
258	archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM);
259	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
260	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
261
262	/*
263	 * Write a regular file with ARCHIVE_EXTRACT_PERM & SUID bit,
264	 * but wrong uid.  POSIX says you shouldn't restore SUID bit
265	 * unless the UID could be restored.
266	 */
267	assert(archive_entry_clear(ae) != NULL);
268	archive_entry_copy_pathname(ae, "file_bad_suid");
269	archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0742);
270	archive_entry_set_uid(ae, getuid() + 1);
271	archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM);
272	assertA(0 == archive_write_header(a, ae));
273	/*
274	 * Because we didn't ask for owner, the failure to
275	 * restore SUID shouldn't return a failure.
276	 * We check below to make sure SUID really wasn't set.
277	 * See more detailed comments below.
278	 */
279	failure("Opportunistic SUID failure shouldn't return error.");
280	assertEqualInt(0, archive_write_finish_entry(a));
281
282        if (getuid() != 0) {
283		assert(archive_entry_clear(ae) != NULL);
284		archive_entry_copy_pathname(ae, "file_bad_suid2");
285		archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0742);
286		archive_entry_set_uid(ae, getuid() + 1);
287		archive_write_disk_set_options(a,
288		    ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_OWNER);
289		assertA(0 == archive_write_header(a, ae));
290		/* Owner change should fail here. */
291		failure("Non-opportunistic SUID failure should return error.");
292		assertEqualInt(ARCHIVE_WARN, archive_write_finish_entry(a));
293	}
294
295	/* Write a regular file with ARCHIVE_EXTRACT_PERM & SGID bit */
296	assert(archive_entry_clear(ae) != NULL);
297	archive_entry_copy_pathname(ae, "file_perm_sgid");
298	archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742);
299	archive_entry_set_gid(ae, defaultgid());
300	archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM);
301	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
302	failure("Setting SGID bit should succeed here.");
303	assertEqualIntA(a, 0, archive_write_finish_entry(a));
304
305	if (altgid() == -1) {
306		/*
307		 * Current user must belong to at least two groups or
308		 * else we can't test setting the GID to another group.
309		 */
310		skipping("Current user can't test gid restore: must belong to more than one group.");
311	} else {
312		/*
313		 * Write a regular file with ARCHIVE_EXTRACT_PERM & SGID bit
314		 * but without ARCHIVE_EXTRACT_OWNER.
315		 */
316		/*
317		 * This is a weird case: The user has asked for permissions to
318		 * be restored but not asked for ownership to be restored.  As
319		 * a result, the default file creation will create a file with
320		 * the wrong group.  There are several possible behaviors for
321		 * libarchive in this scenario:
322		 *  = Set the SGID bit.  It is wrong and a security hole to
323		 *    set SGID with the wrong group.  Even POSIX thinks so.
324		 *  = Implicitly set the group.  I don't like this.
325		 *  = drop the SGID bit and warn (the old libarchive behavior)
326		 *  = drop the SGID bit and don't warn (the current libarchive
327		 *    behavior).
328		 * The current behavior sees SGID/SUID restore when you
329		 * don't ask for owner restore as an "opportunistic"
330		 * action.  That is, libarchive should do it if it can,
331		 * but if it can't, it's not an error.
332		 */
333		assert(archive_entry_clear(ae) != NULL);
334		archive_entry_copy_pathname(ae, "file_alt_sgid");
335		archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742);
336		archive_entry_set_uid(ae, getuid());
337		archive_entry_set_gid(ae, altgid());
338		archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM);
339		assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
340		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.");
341		assertEqualIntA(a, 0, archive_write_finish_entry(a));
342
343		/*
344		 * As above, but add _EXTRACT_OWNER to verify that it
345		 * does succeed.
346		 */
347		assert(archive_entry_clear(ae) != NULL);
348		archive_entry_copy_pathname(ae, "file_alt_sgid_owner");
349		archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742);
350		archive_entry_set_uid(ae, getuid());
351		archive_entry_set_gid(ae, altgid());
352		archive_write_disk_set_options(a,
353		    ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_OWNER);
354		assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
355		failure("Setting SGID bit should succeed here.");
356		assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
357	}
358
359	/*
360	 * Write a regular file with ARCHIVE_EXTRACT_PERM & SGID bit,
361	 * but wrong GID.  POSIX says you shouldn't restore SGID bit
362	 * unless the GID could be restored.
363	 */
364	if (invalidgid() == -1) {
365		/* This test always fails for root. */
366		printf("Running as root: Can't test SGID failures.\n");
367	} else {
368		assert(archive_entry_clear(ae) != NULL);
369		archive_entry_copy_pathname(ae, "file_bad_sgid");
370		archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742);
371		archive_entry_set_gid(ae, invalidgid());
372		archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM);
373		assertA(0 == archive_write_header(a, ae));
374		failure("This SGID restore should fail without an error.");
375		assertEqualIntA(a, 0, archive_write_finish_entry(a));
376
377		assert(archive_entry_clear(ae) != NULL);
378		archive_entry_copy_pathname(ae, "file_bad_sgid2");
379		archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742);
380		archive_entry_set_gid(ae, invalidgid());
381		archive_write_disk_set_options(a,
382		    ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_OWNER);
383		assertA(0 == archive_write_header(a, ae));
384		failure("This SGID restore should fail with an error.");
385		assertEqualIntA(a, ARCHIVE_WARN, archive_write_finish_entry(a));
386	}
387
388	/* Set ownership should fail if we're not root. */
389	if (getuid() == 0) {
390		printf("Running as root: Can't test setuid failures.\n");
391	} else {
392		assert(archive_entry_clear(ae) != NULL);
393		archive_entry_copy_pathname(ae, "file_bad_owner");
394		archive_entry_set_mode(ae, S_IFREG | 0744);
395		archive_entry_set_uid(ae, getuid() + 1);
396		archive_write_disk_set_options(a, ARCHIVE_EXTRACT_OWNER);
397		assertA(0 == archive_write_header(a, ae));
398		assertEqualIntA(a,ARCHIVE_WARN,archive_write_finish_entry(a));
399	}
400
401	assertEqualInt(ARCHIVE_OK, archive_write_free(a));
402	archive_entry_free(ae);
403
404	/* Test the entries on disk. */
405	assertEqualInt(0, stat("file_0755", &st));
406	failure("file_0755: st.st_mode=%o", st.st_mode);
407	assertEqualInt(st.st_mode & 07777, 0755);
408
409	assertEqualInt(0, stat("file_overwrite_0144", &st));
410	failure("file_overwrite_0144: st.st_mode=%o", st.st_mode);
411	assertEqualInt(st.st_mode & 07777, 0144);
412
413	assertEqualInt(0, stat("dir_0514", &st));
414	failure("dir_0514: st.st_mode=%o", st.st_mode);
415	assertEqualInt(st.st_mode & 07777, 0514);
416
417	assertEqualInt(0, stat("dir_overwrite_0744", &st));
418	failure("dir_overwrite_0744: st.st_mode=%o", st.st_mode);
419	assertEqualInt(st.st_mode & 0777, 0744);
420
421	assertEqualInt(0, stat("file_no_suid", &st));
422	failure("file_0755: st.st_mode=%o", st.st_mode);
423	assertEqualInt(st.st_mode & 07777, 0755);
424
425	assertEqualInt(0, stat("file_0777", &st));
426	failure("file_0777: st.st_mode=%o", st.st_mode);
427	assertEqualInt(st.st_mode & 07777, 0777);
428
429	/* SUID bit should get set here. */
430	assertEqualInt(0, stat("file_4742", &st));
431	failure("file_4742: st.st_mode=%o", st.st_mode);
432	assertEqualInt(st.st_mode & 07777, S_ISUID | 0742);
433
434	/* SUID bit should NOT have been set here. */
435	assertEqualInt(0, stat("file_bad_suid", &st));
436	failure("file_bad_suid: st.st_mode=%o", st.st_mode);
437	assertEqualInt(st.st_mode & 07777, 0742);
438
439	/* Some things don't fail if you're root, so suppress this. */
440	if (getuid() != 0) {
441		/* SUID bit should NOT have been set here. */
442		assertEqualInt(0, stat("file_bad_suid2", &st));
443		failure("file_bad_suid2: st.st_mode=%o", st.st_mode);
444		assertEqualInt(st.st_mode & 07777, 0742);
445	}
446
447	/* SGID should be set here. */
448	assertEqualInt(0, stat("file_perm_sgid", &st));
449	failure("file_perm_sgid: st.st_mode=%o", st.st_mode);
450	assertEqualInt(st.st_mode & 07777, S_ISGID | 0742);
451
452	if (altgid() != -1) {
453		/* SGID should not be set here. */
454		assertEqualInt(0, stat("file_alt_sgid", &st));
455		failure("file_alt_sgid: st.st_mode=%o", st.st_mode);
456		assertEqualInt(st.st_mode & 07777, 0742);
457
458		/* SGID should be set here. */
459		assertEqualInt(0, stat("file_alt_sgid_owner", &st));
460		failure("file_alt_sgid: st.st_mode=%o", st.st_mode);
461		assertEqualInt(st.st_mode & 07777, S_ISGID | 0742);
462	}
463
464	if (invalidgid() != -1) {
465		/* SGID should NOT be set here. */
466		assertEqualInt(0, stat("file_bad_sgid", &st));
467		failure("file_bad_sgid: st.st_mode=%o", st.st_mode);
468		assertEqualInt(st.st_mode & 07777, 0742);
469		/* SGID should NOT be set here. */
470		assertEqualInt(0, stat("file_bad_sgid2", &st));
471		failure("file_bad_sgid2: st.st_mode=%o", st.st_mode);
472		assertEqualInt(st.st_mode & 07777, 0742);
473	}
474
475	if (getuid() != 0) {
476		assertEqualInt(0, stat("file_bad_owner", &st));
477		failure("file_bad_owner: st.st_mode=%o", st.st_mode);
478		assertEqualInt(st.st_mode & 07777, 0744);
479		failure("file_bad_owner: st.st_uid=%d getuid()=%d",
480		    st.st_uid, getuid());
481		/* The entry had getuid()+1, but because we're
482		 * not root, we should not have been able to set that. */
483		assertEqualInt(st.st_uid, getuid());
484	}
485#endif
486}
487