]>
Commit | Line | Data |
---|---|---|
1e59de90 TL |
1 | // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- |
2 | // vim: ts=8 sw=2 smarttab | |
3 | /* | |
4 | * Ceph - scalable distributed file system | |
5 | * | |
6 | * Copyright (C) 2023 Red Hat, Inc. | |
7 | * | |
8 | * This is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU Lesser General Public | |
10 | * License version 2.1, as published by the Free Software | |
11 | * Foundation. See file COPYING. | |
12 | * | |
13 | */ | |
14 | ||
15 | #include "gtest/gtest.h" | |
16 | #include "common/ceph_argparse.h" | |
17 | #include "include/buffer.h" | |
18 | #include "include/fs_types.h" | |
19 | #include "include/stringify.h" | |
20 | #include "include/cephfs/libcephfs.h" | |
21 | #include "include/rados/librados.h" | |
22 | #include <errno.h> | |
23 | #include <fcntl.h> | |
24 | #include <unistd.h> | |
25 | #include <sys/types.h> | |
26 | #include <sys/stat.h> | |
27 | #include <dirent.h> | |
28 | #include <sys/uio.h> | |
29 | #include <iostream> | |
30 | #include <vector> | |
31 | #include "json_spirit/json_spirit.h" | |
32 | ||
33 | #ifdef __linux__ | |
34 | #include <limits.h> | |
35 | #include <sys/xattr.h> | |
36 | #endif | |
37 | ||
38 | using namespace std; | |
39 | struct ceph_mount_info *admin; | |
40 | struct ceph_mount_info *cmount; | |
41 | char filename[128]; | |
42 | ||
43 | void run_fallocate_test_case(int mode, int result, bool with_admin=false) | |
44 | { | |
45 | struct ceph_statx stx; | |
46 | int flags = FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE; | |
47 | ||
48 | ASSERT_EQ(0, ceph_chmod(admin, filename, mode)); | |
49 | ||
50 | struct ceph_mount_info *_cmount = cmount; | |
51 | if (with_admin) { | |
52 | _cmount = admin; | |
53 | } | |
54 | int fd = ceph_open(_cmount, filename, O_RDWR, 0); | |
55 | ASSERT_LE(0, fd); | |
56 | ASSERT_EQ(0, ceph_fallocate(_cmount, fd, flags, 1024, 40960)); | |
57 | ASSERT_EQ(ceph_statx(_cmount, filename, &stx, CEPH_STATX_MODE, 0), 0); | |
58 | std::cout << "After ceph_fallocate, mode: 0" << oct << mode << " -> 0" | |
59 | << (stx.stx_mode & 07777) << dec << std::endl; | |
60 | ASSERT_EQ(stx.stx_mode & (S_ISUID|S_ISGID), result); | |
61 | ceph_close(_cmount, fd); | |
62 | } | |
63 | ||
64 | rados_t cluster; | |
65 | ||
66 | int do_mon_command(string s, string *key) | |
67 | { | |
68 | char *outs, *outbuf; | |
69 | size_t outs_len, outbuf_len; | |
70 | const char *ss = s.c_str(); | |
71 | int r = rados_mon_command(cluster, (const char **)&ss, 1, | |
72 | 0, 0, | |
73 | &outbuf, &outbuf_len, | |
74 | &outs, &outs_len); | |
75 | if (outbuf_len) { | |
76 | string s(outbuf, outbuf_len); | |
77 | std::cout << "out: " << s << std::endl; | |
78 | ||
79 | // parse out the key | |
80 | json_spirit::mValue v, k; | |
81 | json_spirit::read_or_throw(s, v); | |
82 | k = v.get_array()[0].get_obj().find("key")->second; | |
83 | *key = k.get_str(); | |
84 | std::cout << "key: " << *key << std::endl; | |
85 | free(outbuf); | |
86 | } else { | |
87 | return -CEPHFS_EINVAL; | |
88 | } | |
89 | if (outs_len) { | |
90 | string s(outs, outs_len); | |
91 | std::cout << "outs: " << s << std::endl; | |
92 | free(outs); | |
93 | } | |
94 | return r; | |
95 | } | |
96 | ||
97 | void run_write_test_case(int mode, int result, bool with_admin=false) | |
98 | { | |
99 | struct ceph_statx stx; | |
100 | ||
101 | ASSERT_EQ(0, ceph_chmod(admin, filename, mode)); | |
102 | ||
103 | struct ceph_mount_info *_cmount = cmount; | |
104 | if (with_admin) { | |
105 | _cmount = admin; | |
106 | } | |
107 | int fd = ceph_open(_cmount, filename, O_RDWR, 0); | |
108 | ASSERT_LE(0, fd); | |
109 | ASSERT_EQ(ceph_write(_cmount, fd, "foo", 3, 0), 3); | |
110 | ASSERT_EQ(ceph_statx(_cmount, filename, &stx, CEPH_STATX_MODE, 0), 0); | |
111 | std::cout << "After ceph_write, mode: 0" << oct << mode << " -> 0" | |
112 | << (stx.stx_mode & 07777) << dec << std::endl; | |
113 | ASSERT_EQ(stx.stx_mode & (S_ISUID|S_ISGID), result); | |
114 | ceph_close(_cmount, fd); | |
115 | } | |
116 | ||
117 | void run_truncate_test_case(int mode, int result, size_t size, bool with_admin=false) | |
118 | { | |
119 | struct ceph_statx stx; | |
120 | ||
121 | ASSERT_EQ(0, ceph_chmod(admin, filename, mode)); | |
122 | ||
123 | struct ceph_mount_info *_cmount = cmount; | |
124 | if (with_admin) { | |
125 | _cmount = admin; | |
126 | } | |
127 | int fd = ceph_open(_cmount, filename, O_RDWR, 0); | |
128 | ASSERT_LE(0, fd); | |
129 | ASSERT_GE(ceph_ftruncate(_cmount, fd, size), 0); | |
130 | ASSERT_EQ(ceph_statx(_cmount, filename, &stx, CEPH_STATX_MODE, 0), 0); | |
131 | std::cout << "After ceph_truncate size " << size << " mode: 0" << oct | |
132 | << mode << " -> 0" << (stx.stx_mode & 07777) << dec << std::endl; | |
133 | ASSERT_EQ(stx.stx_mode & (S_ISUID|S_ISGID), result); | |
134 | ceph_close(_cmount, fd); | |
135 | } | |
136 | ||
137 | TEST(SuidsgidTest, WriteClearSetuid) { | |
138 | ASSERT_EQ(0, ceph_create(&admin, NULL)); | |
139 | ASSERT_EQ(0, ceph_conf_read_file(admin, NULL)); | |
140 | ASSERT_EQ(0, ceph_conf_parse_env(admin, NULL)); | |
141 | ASSERT_EQ(0, ceph_mount(admin, "/")); | |
142 | ||
143 | sprintf(filename, "/clear_suidsgid_file_%d", getpid()); | |
144 | int fd = ceph_open(admin, filename, O_CREAT|O_RDWR, 0766); | |
145 | ASSERT_GE(ceph_ftruncate(admin, fd, 10000000), 0); | |
146 | ceph_close(admin, fd); | |
147 | ||
148 | string user = "clear_suidsgid_" + stringify(rand()); | |
149 | // create access key | |
150 | string key; | |
151 | ASSERT_EQ(0, do_mon_command( | |
152 | "{\"prefix\": \"auth get-or-create\", \"entity\": \"client." + user + "\", " | |
153 | "\"caps\": [\"mon\", \"allow *\", \"osd\", \"allow *\", \"mgr\", \"allow *\", " | |
154 | "\"mds\", \"allow *\"], \"format\": \"json\"}", &key)); | |
155 | ||
156 | ASSERT_EQ(0, ceph_create(&cmount, user.c_str())); | |
157 | ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); | |
158 | ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); | |
159 | ASSERT_EQ(0, ceph_conf_set(cmount, "key", key.c_str())); | |
160 | ASSERT_EQ(ceph_init(cmount), 0); | |
161 | UserPerm *perms = ceph_userperm_new(123, 456, 0, NULL); | |
162 | ASSERT_NE(nullptr, perms); | |
163 | ASSERT_EQ(0, ceph_mount_perms_set(cmount, perms)); | |
164 | ceph_userperm_destroy(perms); | |
165 | ASSERT_EQ(0, ceph_mount(cmount, "/")); | |
166 | ||
167 | // 1, Commit to a non-exec file by an unprivileged user clears suid and sgid. | |
168 | run_fallocate_test_case(06666, 0); // a+rws | |
169 | ||
170 | // 2, Commit to a group-exec file by an unprivileged user clears suid and sgid. | |
171 | run_fallocate_test_case(06676, 0); // g+x,a+rws | |
172 | ||
173 | // 3, Commit to a user-exec file by an unprivileged user clears suid and sgid. | |
174 | run_fallocate_test_case(06766, 0); // u+x,a+rws,g-x | |
175 | ||
176 | // 4, Commit to a all-exec file by an unprivileged user clears suid and sgid. | |
177 | run_fallocate_test_case(06777, 0); // a+rwxs | |
178 | ||
179 | // 5, Commit to a non-exec file by root leaves suid and sgid. | |
180 | run_fallocate_test_case(06666, S_ISUID|S_ISGID, true); // a+rws | |
181 | ||
182 | // 6, Commit to a group-exec file by root leaves suid and sgid. | |
183 | run_fallocate_test_case(06676, S_ISUID|S_ISGID, true); // g+x,a+rws | |
184 | ||
185 | // 7, Commit to a user-exec file by root leaves suid and sgid. | |
186 | run_fallocate_test_case(06766, S_ISUID|S_ISGID, true); // u+x,a+rws,g-x | |
187 | ||
188 | // 8, Commit to a all-exec file by root leaves suid and sgid. | |
189 | run_fallocate_test_case(06777, S_ISUID|S_ISGID, true); // a+rwxs | |
190 | ||
191 | // 9, Commit to a group-exec file by an unprivileged user clears sgid | |
192 | run_fallocate_test_case(02676, 0); // a+rw,g+rwxs | |
193 | ||
194 | // 10, Commit to a all-exec file by an unprivileged user clears sgid. | |
195 | run_fallocate_test_case(02777, 0); // a+rwx,g+rwxs | |
196 | ||
197 | // 11, Write by privileged user leaves the suid and sgid | |
198 | run_write_test_case(06766, S_ISUID | S_ISGID, true); | |
199 | ||
200 | // 12, Write by unprivileged user clears the suid and sgid | |
201 | run_write_test_case(06766, 0); | |
202 | ||
203 | // 13, Truncate by privileged user leaves the suid and sgid | |
204 | run_truncate_test_case(06766, S_ISUID | S_ISGID, 10000, true); | |
205 | ||
206 | // 14, Truncate by unprivileged user clears the suid and sgid | |
207 | run_truncate_test_case(06766, 0, 100); | |
208 | ||
209 | // clean up | |
210 | ceph_shutdown(cmount); | |
211 | ceph_shutdown(admin); | |
212 | } | |
213 | ||
214 | TEST(LibCephFS, ChownClearSetuid) { | |
215 | struct ceph_mount_info *cmount; | |
216 | ASSERT_EQ(ceph_create(&cmount, NULL), 0); | |
217 | ASSERT_EQ(ceph_conf_read_file(cmount, NULL), 0); | |
218 | ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); | |
219 | ASSERT_EQ(ceph_mount(cmount, "/"), 0); | |
220 | ||
221 | Inode *root; | |
222 | ASSERT_EQ(ceph_ll_lookup_root(cmount, &root), 0); | |
223 | ||
224 | char filename[32]; | |
225 | sprintf(filename, "clearsetuid%x", getpid()); | |
226 | ||
227 | Fh *fh; | |
228 | Inode *in; | |
229 | struct ceph_statx stx; | |
230 | const mode_t after_mode = S_IRWXU; | |
231 | const mode_t before_mode = S_IRWXU | S_ISUID | S_ISGID; | |
232 | const unsigned want = CEPH_STATX_UID|CEPH_STATX_GID|CEPH_STATX_MODE; | |
233 | UserPerm *usercred = ceph_mount_perms(cmount); | |
234 | ||
235 | ceph_ll_unlink(cmount, root, filename, usercred); | |
236 | ASSERT_EQ(ceph_ll_create(cmount, root, filename, before_mode, | |
237 | O_RDWR|O_CREAT|O_EXCL, &in, &fh, &stx, want, 0, | |
238 | usercred), 0); | |
239 | ||
240 | ASSERT_EQ(stx.stx_mode & (mode_t)ALLPERMS, before_mode); | |
241 | ||
242 | // chown -- for this we need to be "root" | |
243 | UserPerm *rootcred = ceph_userperm_new(0, 0, 0, NULL); | |
244 | ASSERT_TRUE(rootcred); | |
245 | stx.stx_uid++; | |
246 | stx.stx_gid++; | |
247 | ASSERT_EQ(ceph_ll_setattr(cmount, in, &stx, CEPH_SETATTR_UID|CEPH_SETATTR_GID, rootcred), 0); | |
248 | ASSERT_EQ(ceph_ll_getattr(cmount, in, &stx, CEPH_STATX_MODE, 0, usercred), 0); | |
249 | ASSERT_TRUE(stx.stx_mask & CEPH_STATX_MODE); | |
250 | ASSERT_EQ(stx.stx_mode & (mode_t)ALLPERMS, after_mode); | |
251 | ||
252 | /* test chown with supplementary groups, and chown with/without exe bit */ | |
253 | uid_t u = 65534; | |
254 | gid_t g = 65534; | |
255 | gid_t gids[] = {65533,65532}; | |
256 | UserPerm *altcred = ceph_userperm_new(u, g, sizeof gids / sizeof gids[0], gids); | |
257 | stx.stx_uid = u; | |
258 | stx.stx_gid = g; | |
259 | mode_t m = S_ISGID|S_ISUID|S_IRUSR|S_IWUSR; | |
260 | stx.stx_mode = m; | |
261 | ASSERT_EQ(ceph_ll_setattr(cmount, in, &stx, CEPH_SETATTR_MODE|CEPH_SETATTR_UID|CEPH_SETATTR_GID, rootcred), 0); | |
262 | ASSERT_EQ(ceph_ll_getattr(cmount, in, &stx, CEPH_STATX_MODE, 0, altcred), 0); | |
263 | ASSERT_EQ(stx.stx_mode&(mode_t)ALLPERMS, m); | |
264 | /* not dropped without exe bit */ | |
265 | stx.stx_gid = gids[0]; | |
266 | ASSERT_EQ(ceph_ll_setattr(cmount, in, &stx, CEPH_SETATTR_GID, altcred), 0); | |
267 | ASSERT_EQ(ceph_ll_getattr(cmount, in, &stx, CEPH_STATX_MODE, 0, altcred), 0); | |
268 | ASSERT_EQ(stx.stx_mode&(mode_t)ALLPERMS, m); | |
269 | /* now check dropped with exe bit */ | |
270 | m = S_ISGID|S_ISUID|S_IRWXU; | |
271 | stx.stx_mode = m; | |
272 | ASSERT_EQ(ceph_ll_setattr(cmount, in, &stx, CEPH_STATX_MODE, altcred), 0); | |
273 | ASSERT_EQ(ceph_ll_getattr(cmount, in, &stx, CEPH_STATX_MODE, 0, altcred), 0); | |
274 | ASSERT_EQ(stx.stx_mode&(mode_t)ALLPERMS, m); | |
275 | stx.stx_gid = gids[1]; | |
276 | ASSERT_EQ(ceph_ll_setattr(cmount, in, &stx, CEPH_SETATTR_GID, altcred), 0); | |
277 | ASSERT_EQ(ceph_ll_getattr(cmount, in, &stx, CEPH_STATX_MODE, 0, altcred), 0); | |
278 | ASSERT_EQ(stx.stx_mode&(mode_t)ALLPERMS, m&(S_IRWXU|S_IRWXG|S_IRWXO)); | |
279 | ceph_userperm_destroy(altcred); | |
280 | ||
281 | ASSERT_EQ(ceph_ll_close(cmount, fh), 0); | |
282 | ceph_shutdown(cmount); | |
283 | } | |
284 | ||
285 | static int update_root_mode() | |
286 | { | |
287 | struct ceph_mount_info *admin; | |
288 | int r = ceph_create(&admin, NULL); | |
289 | if (r < 0) | |
290 | return r; | |
291 | ceph_conf_read_file(admin, NULL); | |
292 | ceph_conf_parse_env(admin, NULL); | |
293 | ceph_conf_set(admin, "client_permissions", "false"); | |
294 | r = ceph_mount(admin, "/"); | |
295 | if (r < 0) | |
296 | goto out; | |
297 | r = ceph_chmod(admin, "/", 0777); | |
298 | out: | |
299 | ceph_shutdown(admin); | |
300 | return r; | |
301 | } | |
302 | ||
303 | int main(int argc, char **argv) | |
304 | { | |
305 | int r = update_root_mode(); | |
306 | if (r < 0) | |
307 | exit(1); | |
308 | ||
309 | ::testing::InitGoogleTest(&argc, argv); | |
310 | ||
311 | srand(getpid()); | |
312 | ||
313 | r = rados_create(&cluster, NULL); | |
314 | if (r < 0) | |
315 | exit(1); | |
316 | ||
317 | r = rados_conf_read_file(cluster, NULL); | |
318 | if (r < 0) | |
319 | exit(1); | |
320 | ||
321 | rados_conf_parse_env(cluster, NULL); | |
322 | r = rados_connect(cluster); | |
323 | if (r < 0) | |
324 | exit(1); | |
325 | ||
326 | r = RUN_ALL_TESTS(); | |
327 | ||
328 | rados_shutdown(cluster); | |
329 | ||
330 | return r; | |
331 | } |