1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "sandbox/linux/syscall_broker/broker_file_permission.h"
6 
7 #include <fcntl.h>
8 #include <string.h>
9 #include <sys/stat.h>
10 #include <sys/types.h>
11 #include <unistd.h>
12 
13 #include "base/check.h"
14 #include "base/macros.h"
15 #include "base/notreached.h"
16 #include "sandbox/linux/tests/test_utils.h"
17 #include "sandbox/linux/tests/unit_tests.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19 
20 namespace sandbox {
21 
22 namespace syscall_broker {
23 
24 class BrokerFilePermissionTester {
25  public:
ValidatePath(const char * path)26   static bool ValidatePath(const char* path) {
27     return BrokerFilePermission::ValidatePath(path);
28   }
GetErrorMessage()29   static const char* GetErrorMessage() {
30     return BrokerFilePermission::GetErrorMessageForTests();
31   }
32 
33  private:
34   DISALLOW_COPY_AND_ASSIGN(BrokerFilePermissionTester);
35 };
36 
37 namespace {
38 
39 // Creation tests are DEATH tests as a bad permission causes termination.
SANDBOX_TEST(BrokerFilePermission,CreateGood)40 SANDBOX_TEST(BrokerFilePermission, CreateGood) {
41   const char kPath[] = "/tmp/good";
42   BrokerFilePermission perm = BrokerFilePermission::ReadOnly(kPath);
43 }
44 
SANDBOX_TEST(BrokerFilePermission,CreateGoodRecursive)45 SANDBOX_TEST(BrokerFilePermission, CreateGoodRecursive) {
46   const char kPath[] = "/tmp/good/";
47   BrokerFilePermission perm = BrokerFilePermission::ReadOnlyRecursive(kPath);
48 }
49 
50 // In official builds, CHECK(x) causes a SIGTRAP on the architectures where the
51 // sanbox is enabled (that are x86, x86_64, arm64 and 32-bit arm processes
52 // running on a arm64 kernel).
53 #if defined(OFFICIAL_BUILD)
54 #define DEATH_BY_CHECK(msg) DEATH_BY_SIGNAL(SIGTRAP)
55 #else
56 #define DEATH_BY_CHECK(msg) DEATH_MESSAGE(msg)
57 #endif
58 
SANDBOX_DEATH_TEST(BrokerFilePermission,CreateBad,DEATH_BY_CHECK (BrokerFilePermissionTester::GetErrorMessage ()))59 SANDBOX_DEATH_TEST(
60     BrokerFilePermission,
61     CreateBad,
62     DEATH_BY_CHECK(BrokerFilePermissionTester::GetErrorMessage())) {
63   const char kPath[] = "/tmp/bad/";
64   BrokerFilePermission perm = BrokerFilePermission::ReadOnly(kPath);
65 }
66 
SANDBOX_DEATH_TEST(BrokerFilePermission,CreateBadRecursive,DEATH_BY_CHECK (BrokerFilePermissionTester::GetErrorMessage ()))67 SANDBOX_DEATH_TEST(
68     BrokerFilePermission,
69     CreateBadRecursive,
70     DEATH_BY_CHECK(BrokerFilePermissionTester::GetErrorMessage())) {
71   const char kPath[] = "/tmp/bad";
72   BrokerFilePermission perm = BrokerFilePermission::ReadOnlyRecursive(kPath);
73 }
74 
SANDBOX_DEATH_TEST(BrokerFilePermission,CreateBadNotAbs,DEATH_BY_CHECK (BrokerFilePermissionTester::GetErrorMessage ()))75 SANDBOX_DEATH_TEST(
76     BrokerFilePermission,
77     CreateBadNotAbs,
78     DEATH_BY_CHECK(BrokerFilePermissionTester::GetErrorMessage())) {
79   const char kPath[] = "tmp/bad";
80   BrokerFilePermission perm = BrokerFilePermission::ReadOnly(kPath);
81 }
82 
SANDBOX_DEATH_TEST(BrokerFilePermission,CreateBadEmpty,DEATH_BY_CHECK (BrokerFilePermissionTester::GetErrorMessage ()))83 SANDBOX_DEATH_TEST(
84     BrokerFilePermission,
85     CreateBadEmpty,
86     DEATH_BY_CHECK(BrokerFilePermissionTester::GetErrorMessage())) {
87   const char kPath[] = "";
88   BrokerFilePermission perm = BrokerFilePermission::ReadOnly(kPath);
89 }
90 
91 // CheckPerm tests |path| against |perm| given |access_flags|.
92 // If |create| is true then file creation is tested for success.
CheckPerm(const BrokerFilePermission & perm,const char * path,int access_flags,bool create)93 void CheckPerm(const BrokerFilePermission& perm,
94                const char* path,
95                int access_flags,
96                bool create) {
97   const char* file_to_open = NULL;
98 
99   ASSERT_FALSE(perm.CheckAccess(path, X_OK, NULL));
100   ASSERT_TRUE(perm.CheckAccess(path, F_OK, NULL));
101   // check bad perms
102   switch (access_flags) {
103     case O_RDONLY:
104       ASSERT_TRUE(perm.CheckOpen(path, O_RDONLY, &file_to_open, NULL));
105       ASSERT_FALSE(perm.CheckOpen(path, O_WRONLY, &file_to_open, NULL));
106       ASSERT_FALSE(perm.CheckOpen(path, O_RDWR, &file_to_open, NULL));
107       ASSERT_TRUE(perm.CheckAccess(path, R_OK, NULL));
108       ASSERT_FALSE(perm.CheckAccess(path, W_OK, NULL));
109       break;
110     case O_WRONLY:
111       ASSERT_FALSE(perm.CheckOpen(path, O_RDONLY, &file_to_open, NULL));
112       ASSERT_TRUE(perm.CheckOpen(path, O_WRONLY, &file_to_open, NULL));
113       ASSERT_FALSE(perm.CheckOpen(path, O_RDWR, &file_to_open, NULL));
114       ASSERT_FALSE(perm.CheckAccess(path, R_OK, NULL));
115       ASSERT_TRUE(perm.CheckAccess(path, W_OK, NULL));
116       break;
117     case O_RDWR:
118       ASSERT_TRUE(perm.CheckOpen(path, O_RDONLY, &file_to_open, NULL));
119       ASSERT_TRUE(perm.CheckOpen(path, O_WRONLY, &file_to_open, NULL));
120       ASSERT_TRUE(perm.CheckOpen(path, O_RDWR, &file_to_open, NULL));
121       ASSERT_TRUE(perm.CheckAccess(path, R_OK, NULL));
122       ASSERT_TRUE(perm.CheckAccess(path, W_OK, NULL));
123       break;
124     default:
125       // Bad test case
126       NOTREACHED();
127   }
128 
129 // O_SYNC can be defined as (__O_SYNC|O_DSYNC)
130 #ifdef O_DSYNC
131   const int kSyncFlag = O_SYNC & ~O_DSYNC;
132 #else
133   const int kSyncFlag = O_SYNC;
134 #endif
135 
136   const int kNumberOfBitsInOAccMode = 2;
137   static_assert(O_ACCMODE == ((1 << kNumberOfBitsInOAccMode) - 1),
138                 "incorrect number of bits");
139   // check every possible flag and act accordingly.
140   // Skipping AccMode bits as they are present in every case.
141   for (int i = kNumberOfBitsInOAccMode; i < 32; i++) {
142     int flag = 1 << i;
143     switch (flag) {
144       case O_APPEND:
145       case O_ASYNC:
146       case O_DIRECT:
147       case O_DIRECTORY:
148 #ifdef O_DSYNC
149       case O_DSYNC:
150 #endif
151       case O_EXCL:
152       case O_LARGEFILE:
153       case O_NOATIME:
154       case O_NOCTTY:
155       case O_NOFOLLOW:
156       case O_NONBLOCK:
157 #if (O_NONBLOCK != O_NDELAY)
158       case O_NDELAY:
159 #endif
160       case kSyncFlag:
161         ASSERT_TRUE(
162             perm.CheckOpen(path, access_flags | flag, &file_to_open, NULL));
163         break;
164       case O_TRUNC: {
165         // The effect of (O_RDONLY | O_TRUNC) is undefined, and in some cases it
166         // actually truncates, so deny.
167         bool result =
168             perm.CheckOpen(path, access_flags | flag, &file_to_open, NULL);
169         if (access_flags == O_RDONLY) {
170           ASSERT_FALSE(result);
171         } else {
172           ASSERT_TRUE(result);
173         }
174         break;
175       }
176       case O_CREAT:
177         continue;  // Handled below.
178       case O_CLOEXEC:
179       default:
180         ASSERT_FALSE(
181             perm.CheckOpen(path, access_flags | flag, &file_to_open, NULL));
182     }
183   }
184   if (create) {
185     bool unlink;
186     ASSERT_TRUE(perm.CheckOpen(path, O_CREAT | O_EXCL | access_flags,
187                                &file_to_open, &unlink));
188     ASSERT_FALSE(unlink);
189   } else {
190     ASSERT_FALSE(perm.CheckOpen(path, O_CREAT | O_EXCL | access_flags,
191                                 &file_to_open, NULL));
192   }
193 }
194 
TEST(BrokerFilePermission,ReadOnly)195 TEST(BrokerFilePermission, ReadOnly) {
196   const char kPath[] = "/tmp/good";
197   BrokerFilePermission perm = BrokerFilePermission::ReadOnly(kPath);
198   CheckPerm(perm, kPath, O_RDONLY, false);
199   // Don't do anything here, so that ASSERT works in the subfunction as
200   // expected.
201 }
202 
TEST(BrokerFilePermission,ReadOnlyRecursive)203 TEST(BrokerFilePermission, ReadOnlyRecursive) {
204   const char kPath[] = "/tmp/good/";
205   const char kPathFile[] = "/tmp/good/file";
206   BrokerFilePermission perm = BrokerFilePermission::ReadOnlyRecursive(kPath);
207   CheckPerm(perm, kPathFile, O_RDONLY, false);
208   // Don't do anything here, so that ASSERT works in the subfunction as
209   // expected.
210 }
211 
212 // Explicit test for O_RDONLY|O_TRUNC, which should be denied due to
213 // undefined behavior.
TEST(BrokerFilePermission,ReadOnlyTruncate)214 TEST(BrokerFilePermission, ReadOnlyTruncate) {
215   const char kPath[] = "/tmp/good";
216   BrokerFilePermission perm = BrokerFilePermission::ReadOnly(kPath);
217   ASSERT_FALSE(perm.CheckOpen(kPath, O_RDONLY | O_TRUNC, nullptr, nullptr));
218 }
219 
TEST(BrokerFilePermission,WriteOnly)220 TEST(BrokerFilePermission, WriteOnly) {
221   const char kPath[] = "/tmp/good";
222   BrokerFilePermission perm = BrokerFilePermission::WriteOnly(kPath);
223   CheckPerm(perm, kPath, O_WRONLY, false);
224   // Don't do anything here, so that ASSERT works in the subfunction as
225   // expected.
226 }
227 
TEST(BrokerFilePermission,ReadWrite)228 TEST(BrokerFilePermission, ReadWrite) {
229   const char kPath[] = "/tmp/good";
230   BrokerFilePermission perm = BrokerFilePermission::ReadWrite(kPath);
231   CheckPerm(perm, kPath, O_RDWR, false);
232   // Don't do anything here, so that ASSERT works in the subfunction as
233   // expected.
234 }
235 
TEST(BrokerFilePermission,ReadWriteCreate)236 TEST(BrokerFilePermission, ReadWriteCreate) {
237   const char kPath[] = "/tmp/good";
238   BrokerFilePermission perm = BrokerFilePermission::ReadWriteCreate(kPath);
239   CheckPerm(perm, kPath, O_RDWR, true);
240   // Don't do anything here, so that ASSERT works in the subfunction as
241   // expected.
242 }
243 
CheckUnlink(BrokerFilePermission & perm,const char * path,int access_flags)244 void CheckUnlink(BrokerFilePermission& perm,
245                  const char* path,
246                  int access_flags) {
247   bool unlink;
248   ASSERT_FALSE(perm.CheckOpen(path, access_flags, NULL, &unlink));
249   ASSERT_TRUE(
250       perm.CheckOpen(path, access_flags | O_CREAT | O_EXCL, NULL, &unlink));
251   ASSERT_TRUE(unlink);
252 }
253 
TEST(BrokerFilePermission,ReadWriteCreateTemporaryRecursive)254 TEST(BrokerFilePermission, ReadWriteCreateTemporaryRecursive) {
255   const char kPath[] = "/tmp/good/";
256   const char kPathFile[] = "/tmp/good/file";
257   BrokerFilePermission perm =
258       BrokerFilePermission::ReadWriteCreateTemporaryRecursive(kPath);
259   CheckUnlink(perm, kPathFile, O_RDWR);
260   // Don't do anything here, so that ASSERT works in the subfunction as
261   // expected.
262 }
263 
TEST(BrokerFilePermission,StatOnlyWithIntermediateDirs)264 TEST(BrokerFilePermission, StatOnlyWithIntermediateDirs) {
265   const char kPath[] = "/tmp/good/path";
266   const char kLeading1[] = "/";
267   const char kLeading2[] = "/tmp";
268   const char kLeading3[] = "/tmp/good/path";
269   const char kTrailing[] = "/tmp/good/path/bad";
270 
271   BrokerFilePermission perm =
272       BrokerFilePermission::StatOnlyWithIntermediateDirs(kPath);
273   // No open or access permission.
274   ASSERT_FALSE(perm.CheckOpen(kPath, O_RDONLY, NULL, NULL));
275   ASSERT_FALSE(perm.CheckOpen(kPath, O_WRONLY, NULL, NULL));
276   ASSERT_FALSE(perm.CheckOpen(kPath, O_RDWR, NULL, NULL));
277   ASSERT_FALSE(perm.CheckAccess(kPath, R_OK, NULL));
278   ASSERT_FALSE(perm.CheckAccess(kPath, W_OK, NULL));
279 
280   // Stat for all leading paths, but not trailing paths.
281   ASSERT_TRUE(perm.CheckStat(kPath, NULL));
282   ASSERT_TRUE(perm.CheckStat(kLeading1, NULL));
283   ASSERT_TRUE(perm.CheckStat(kLeading2, NULL));
284   ASSERT_TRUE(perm.CheckStat(kLeading3, NULL));
285   ASSERT_FALSE(perm.CheckStat(kTrailing, NULL));
286 }
287 
TEST(BrokerFilePermission,ValidatePath)288 TEST(BrokerFilePermission, ValidatePath) {
289   EXPECT_TRUE(BrokerFilePermissionTester::ValidatePath("/path"));
290   EXPECT_TRUE(BrokerFilePermissionTester::ValidatePath("/"));
291   EXPECT_TRUE(BrokerFilePermissionTester::ValidatePath("/..path"));
292 
293   EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath(""));
294   EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath("bad"));
295   EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath("/bad/"));
296   EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath("bad/"));
297   EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath("/bad/.."));
298   EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath("/bad/../bad"));
299   EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath("/../bad"));
300 }
301 
302 }  // namespace
303 
304 }  // namespace syscall_broker
305 
306 }  // namespace sandbox
307