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