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$");
27 
28 #define UMASK 022
29 
30 /*
31  * Exercise security checks that should prevent certain
32  * writes.
33  */
34 
35 DEFINE_TEST(test_write_disk_secure)
36 {
37 #if defined(_WIN32) && !defined(__CYGWIN__)
38 	skipping("archive_write_disk security checks not supported on Windows");
39 #else
40 	struct archive *a;
41 	struct archive_entry *ae;
42 	struct stat st;
43 
44 	/* Start with a known umask. */
45 	assertUmask(UMASK);
46 
47 	/* Create an archive_write_disk object. */
48 	assert((a = archive_write_disk_new()) != NULL);
49 
50 	/* Write a regular dir to it. */
51 	assert((ae = archive_entry_new()) != NULL);
52 	archive_entry_copy_pathname(ae, "dir");
53 	archive_entry_set_mode(ae, S_IFDIR | 0777);
54 	assert(0 == archive_write_header(a, ae));
55 	archive_entry_free(ae);
56 	assert(0 == archive_write_finish_entry(a));
57 
58 	/* Write a symlink to the dir above. */
59 	assert((ae = archive_entry_new()) != NULL);
60 	archive_entry_copy_pathname(ae, "link_to_dir");
61 	archive_entry_set_mode(ae, S_IFLNK | 0777);
62 	archive_entry_set_symlink(ae, "dir");
63 	archive_write_disk_set_options(a, 0);
64 	assert(0 == archive_write_header(a, ae));
65 	assert(0 == archive_write_finish_entry(a));
66 
67 	/*
68 	 * Without security checks, we should be able to
69 	 * extract a file through the link.
70 	 */
71 	assert(archive_entry_clear(ae) != NULL);
72 	archive_entry_copy_pathname(ae, "link_to_dir/filea");
73 	archive_entry_set_mode(ae, S_IFREG | 0777);
74 	assert(0 == archive_write_header(a, ae));
75 	assert(0 == archive_write_finish_entry(a));
76 
77 	/* But with security checks enabled, this should fail. */
78 	assert(archive_entry_clear(ae) != NULL);
79 	archive_entry_copy_pathname(ae, "link_to_dir/fileb");
80 	archive_entry_set_mode(ae, S_IFREG | 0777);
81 	archive_write_disk_set_options(a, ARCHIVE_EXTRACT_SECURE_SYMLINKS);
82 	failure("Extracting a file through a symlink should fail here.");
83 	assertEqualInt(ARCHIVE_FAILED, archive_write_header(a, ae));
84 	archive_entry_free(ae);
85 	assert(0 == archive_write_finish_entry(a));
86 
87 	/* Create another link. */
88 	assert((ae = archive_entry_new()) != NULL);
89 	archive_entry_copy_pathname(ae, "link_to_dir2");
90 	archive_entry_set_mode(ae, S_IFLNK | 0777);
91 	archive_entry_set_symlink(ae, "dir");
92 	archive_write_disk_set_options(a, 0);
93 	assert(0 == archive_write_header(a, ae));
94 	assert(0 == archive_write_finish_entry(a));
95 
96 	/*
97 	 * With symlink check and unlink option, it should remove
98 	 * the link and create the dir.
99 	 */
100 	assert(archive_entry_clear(ae) != NULL);
101 	archive_entry_copy_pathname(ae, "link_to_dir2/filec");
102 	archive_entry_set_mode(ae, S_IFREG | 0777);
103 	archive_write_disk_set_options(a, ARCHIVE_EXTRACT_SECURE_SYMLINKS | ARCHIVE_EXTRACT_UNLINK);
104 	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
105 	archive_entry_free(ae);
106 	assert(0 == archive_write_finish_entry(a));
107 
108 	/*
109 	 * Without security checks, extracting a dir over a link to a
110 	 * dir should follow the link.
111 	 */
112 	/* Create a symlink to a dir. */
113 	assert((ae = archive_entry_new()) != NULL);
114 	archive_entry_copy_pathname(ae, "link_to_dir3");
115 	archive_entry_set_mode(ae, S_IFLNK | 0777);
116 	archive_entry_set_symlink(ae, "dir");
117 	archive_write_disk_set_options(a, 0);
118 	assert(0 == archive_write_header(a, ae));
119 	assert(0 == archive_write_finish_entry(a));
120 	/* Extract a dir whose name matches the symlink. */
121 	assert(archive_entry_clear(ae) != NULL);
122 	archive_entry_copy_pathname(ae, "link_to_dir3");
123 	archive_entry_set_mode(ae, S_IFDIR | 0777);
124 	assert(0 == archive_write_header(a, ae));
125 	assert(0 == archive_write_finish_entry(a));
126 	/* Verify link was followed. */
127 	assertEqualInt(0, lstat("link_to_dir3", &st));
128 	assert(S_ISLNK(st.st_mode));
129 	archive_entry_free(ae);
130 
131 	/*
132 	 * As above, but a broken link, so the link should get replaced.
133 	 */
134 	/* Create a symlink to a dir. */
135 	assert((ae = archive_entry_new()) != NULL);
136 	archive_entry_copy_pathname(ae, "link_to_dir4");
137 	archive_entry_set_mode(ae, S_IFLNK | 0777);
138 	archive_entry_set_symlink(ae, "nonexistent_dir");
139 	archive_write_disk_set_options(a, 0);
140 	assert(0 == archive_write_header(a, ae));
141 	assert(0 == archive_write_finish_entry(a));
142 	/* Extract a dir whose name matches the symlink. */
143 	assert(archive_entry_clear(ae) != NULL);
144 	archive_entry_copy_pathname(ae, "link_to_dir4");
145 	archive_entry_set_mode(ae, S_IFDIR | 0777);
146 	assert(0 == archive_write_header(a, ae));
147 	assert(0 == archive_write_finish_entry(a));
148 	/* Verify link was replaced. */
149 	assertEqualInt(0, lstat("link_to_dir4", &st));
150 	assert(S_ISDIR(st.st_mode));
151 	archive_entry_free(ae);
152 
153 	/*
154 	 * As above, but a link to a non-dir, so the link should get replaced.
155 	 */
156 	/* Create a regular file and a symlink to it */
157 	assert((ae = archive_entry_new()) != NULL);
158 	archive_entry_copy_pathname(ae, "non_dir");
159 	archive_entry_set_mode(ae, S_IFREG | 0777);
160 	archive_write_disk_set_options(a, 0);
161 	assert(0 == archive_write_header(a, ae));
162 	assert(0 == archive_write_finish_entry(a));
163 	/* Create symlink to the file. */
164 	archive_entry_copy_pathname(ae, "link_to_dir5");
165 	archive_entry_set_mode(ae, S_IFLNK | 0777);
166 	archive_entry_set_symlink(ae, "non_dir");
167 	archive_write_disk_set_options(a, 0);
168 	assert(0 == archive_write_header(a, ae));
169 	assert(0 == archive_write_finish_entry(a));
170 	/* Extract a dir whose name matches the symlink. */
171 	assert(archive_entry_clear(ae) != NULL);
172 	archive_entry_copy_pathname(ae, "link_to_dir5");
173 	archive_entry_set_mode(ae, S_IFDIR | 0777);
174 	assert(0 == archive_write_header(a, ae));
175 	assert(0 == archive_write_finish_entry(a));
176 	/* Verify link was replaced. */
177 	assertEqualInt(0, lstat("link_to_dir5", &st));
178 	assert(S_ISDIR(st.st_mode));
179 	archive_entry_free(ae);
180 
181 	assertEqualInt(ARCHIVE_OK, archive_write_free(a));
182 
183 	/* Test the entries on disk. */
184 	assert(0 == lstat("dir", &st));
185 	failure("dir: st.st_mode=%o", st.st_mode);
186 	assert((st.st_mode & 0777) == 0755);
187 
188 	assert(0 == lstat("link_to_dir", &st));
189 	failure("link_to_dir: st.st_mode=%o", st.st_mode);
190 	assert(S_ISLNK(st.st_mode));
191 #if HAVE_LCHMOD
192 	/* Systems that lack lchmod() can't set symlink perms, so skip this. */
193 	failure("link_to_dir: st.st_mode=%o", st.st_mode);
194 	assert((st.st_mode & 07777) == 0755);
195 #endif
196 
197 	assert(0 == lstat("dir/filea", &st));
198 	failure("dir/filea: st.st_mode=%o", st.st_mode);
199 	assert((st.st_mode & 07777) == 0755);
200 
201 	failure("dir/fileb: This file should not have been created");
202 	assert(0 != lstat("dir/fileb", &st));
203 
204 	assert(0 == lstat("link_to_dir2", &st));
205 	failure("link_to_dir2 should have been re-created as a true dir");
206 	assert(S_ISDIR(st.st_mode));
207 	failure("link_to_dir2: Implicit dir creation should obey umask, but st.st_mode=%o", st.st_mode);
208 	assert((st.st_mode & 0777) == 0755);
209 
210 	assert(0 == lstat("link_to_dir2/filec", &st));
211 	assert(S_ISREG(st.st_mode));
212 	failure("link_to_dir2/filec: st.st_mode=%o", st.st_mode);
213 	assert((st.st_mode & 07777) == 0755);
214 #endif
215 }
216