]> git.proxmox.com Git - ceph.git/blame - ceph/src/test/libcephfs/suidsgid.cc
update ceph source to reef 18.1.2
[ceph.git] / ceph / src / test / libcephfs / suidsgid.cc
CommitLineData
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
38using namespace std;
39struct ceph_mount_info *admin;
40struct ceph_mount_info *cmount;
41char filename[128];
42
43void 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
64rados_t cluster;
65
66int 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
97void 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
117void 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
137TEST(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
214TEST(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
285static 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);
298out:
299 ceph_shutdown(admin);
300 return r;
301}
302
303int 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}