jaulib v1.3.0
Jau Support Library (C++, Java, ..)
testsudo_fileutils02.cpp
Go to the documentation of this file.
1/*
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2021 Gothel Software e.K.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining
6 * a copy of this software and associated documentation files (the
7 * "Software"), to deal in the Software without restriction, including
8 * without limitation the rights to use, copy, modify, merge, publish,
9 * distribute, sublicense, and/or sell copies of the Software, and to
10 * permit persons to whom the Software is furnished to do so, subject to
11 * the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 */
24
26
27#include "jau/os/user_info.hpp"
28
29extern "C" {
30 #include <unistd.h>
31 #include <grp.h>
32 #include <pwd.h>
33 #include <sys/types.h>
34 #include <sys/mount.h>
35 #include <sys/capability.h>
36 #include <sys/prctl.h>
37}
38
40 private:
41 static constexpr const bool change_caps = false;
42
43 static void print_creds(const std::string& title) {
44 jau::fprintf_td(stderr, "%s: uid %" PRIu32 ", euid %" PRIu32 ", gid %" PRIu32 ", egid %" PRIu32 "\n",
45 title.c_str(), ::getuid(), ::geteuid(), ::getgid(), ::getegid());
46
47 gid_t gid_list[64];
48 int count = ::getgroups(sizeof(gid_list)/sizeof(*gid_list), gid_list);
49 if( 0 > count ) {
50 ERR_PRINT("getgroups() failed");
51 } else {
52 jau::fprintf_td(stderr, "%s: groups[%d]: ", title.c_str(), count);
53 for(int i=0; i<count; ++i) {
54 fprintf(stderr, "%" PRIu32 ", ", gid_list[i]);
55 }
56 fprintf(stderr, "\n");
57 }
58
59 }
60 static bool set_groups(size_t size, const gid_t *list) {
61 if( 0 > ::setgroups(size, list) ) {
62 ERR_PRINT("setgroups failed");
63 return false;
64 }
65 return true;
66 }
67
68 static bool set_effective_gid(::gid_t group_id) {
69 if( 0 != ::setegid(group_id) ) {
70 ERR_PRINT("setegid(%" PRIu32 ") failed", group_id);
71 return false;
72 }
73 return true;
74 }
75
76 static bool set_effective_uid(::uid_t user_id) {
77 if( 0 != ::seteuid(user_id) ) {
78 ERR_PRINT("seteuid(%" PRIu32 ") failed", user_id);
79 return false;
80 }
81 return true;
82 }
83
84 public:
85
86 static bool cap_get_flag(cap_t cap_p, cap_value_t cap, cap_flag_t flag, cap_flag_value_t *value_p) noexcept {
87 if( 0 != ::cap_get_flag(cap_p, cap, flag, value_p) ) {
88 ERR_PRINT("cap_get_flag() failed");
89 return false;
90 }
91 return true;
92 }
93 static bool cap_set_flag(cap_t cap_p, cap_flag_t flag, int ncap, const cap_value_t *caps, cap_flag_value_t value) noexcept {
94 if( 0 != ::cap_set_flag(cap_p, flag, ncap, caps, value) ) {
95 ERR_PRINT("cap_set_flag() failed");
96 return false;
97 }
98 return true;
99 }
100
101 static bool cap_set_proc_flag(const std::string& title, cap_flag_t flag, int ncap, const cap_value_t *cap_list) noexcept {
102 ::cap_t cap_p;
103 cap_p = ::cap_get_proc();
104 if( nullptr == cap_p ) {
105 ERR_PRINT("cap_get_proc() failed");
106 return false;
107 }
108 if( !cap_set_flag(cap_p, flag, ncap, cap_list, CAP_SET) ) { return false; }
109 ::cap_set_proc(cap_p);
110 {
111 char* c_str = ::cap_to_text(cap_p, nullptr);
112 jau::fprintf_td(stderr, "%s: set caps %s\n", title.c_str(), c_str);
113 ::cap_free(c_str);
114 }
115 ::cap_free(cap_p);
116 return true;
117 }
118
119 static void print_caps(const std::string& title) {
120 ::cap_t caps;
121 caps = ::cap_get_proc();
122 if( nullptr == caps ) {
123 ERR_PRINT("cap_get_proc() failed");
124 return;
125 }
126 {
127 char* c_str = ::cap_to_text(caps, nullptr);
128 jau::fprintf_td(stderr, "%s: caps %s\n", title.c_str(), c_str);
129 ::cap_free(c_str);
130 }
131 ::cap_free(caps);
132 }
133
134 /**
135 * Get group-id by groupname using system commands `getent` and `cut`.
136 *
137 * Known group-ids are
138 * - Ubuntu, Debian group 24: cdrom
139 * - FreeBSD, Ubuntu, Debian group 44: video
140 * - Alpine/Linux group 27: video
141 */
142 static ::gid_t get_gid(const std::string& groupname) {
143 static const ::gid_t default_group = 44;
144 std::string cmd("getent group "+groupname+" | cut -d: -f3");
145 FILE* fp = ::popen(cmd.c_str(), "r");
146 if (fp == nullptr) {
147 fprintf(stderr, "Command failed (1) '%s'\n", cmd.c_str() );
148 return default_group;
149 }
150 char result[100];
151 ::gid_t result_int = default_group;
152 if( nullptr != ::fgets(result, sizeof(result), fp) ) {
153 result_int = static_cast<::gid_t>( ::atoi(result) );
154 jau::PLAIN_PRINT(true, "get_gid(%s) -> %s (%d)", groupname.c_str(), result, (int)result_int);
155 } else {
156 fprintf(stderr, "Command failed (2) '%s'\n", cmd.c_str() );
157 }
158 ::pclose(fp);
159 return result_int;
160 }
161
163 INFO_STR("\n\ntest50_mount_copy_r_p\n");
164 // ::cap_value_t cap_list[] = { CAP_SYS_ADMIN, CAP_SETUID, CAP_SETGID, CAP_CHOWN, CAP_FOWNER };
165 ::cap_value_t cap_list[] = { CAP_SYS_ADMIN, CAP_SETUID, CAP_SETGID };
166 const size_t cap_list_size = sizeof(cap_list) / sizeof(*cap_list);
167
168 const ::uid_t super_uid = 0;
169 ::uid_t caller_uid = ::getuid();
170
171 ::uid_t user_id = caller_uid;
172 jau::os::UserInfo user_info(user_id);
173 if( !user_info.isValid() ) {
174 ERR_PRINT("couldn't fetch [SUDO_]UID");
175 return;
176 }
177 ::gid_t group_id = (::gid_t)user_info.gid();
178 ::gid_t group_list[] = { user_id, group_id, get_gid("video") };
179
180 const bool setuid_user_to_root = super_uid != caller_uid;
181 if( setuid_user_to_root ) {
182 print_creds("user level - setuid user -> root");
183
184 ::cap_t caps;
185 caps = ::cap_get_proc();
186 if( nullptr == caps ) {
187 ERR_PRINT("cap_get_proc() failed");
188 return;
189 }
190 {
191 char* c_str = ::cap_to_text(caps, nullptr);
192 jau::fprintf_td(stderr, "user level: caps %s\n", c_str);
193 ::cap_free(c_str);
194 }
195 cap_flag_value_t cap_sys_admin, cap_setuid, cap_setgid;
196 if( !cap_get_flag(caps, CAP_SYS_ADMIN, ::CAP_EFFECTIVE, &cap_sys_admin) ) { return; }
197 if( !cap_get_flag(caps, CAP_SETUID, ::CAP_EFFECTIVE, &cap_setuid) ) { return; }
198 if( !cap_get_flag(caps, CAP_SETGID, ::CAP_EFFECTIVE, &cap_setgid) ) { return; }
199 jau::fprintf_td(stderr, "Caps: sys_admin %d, setuid %d, setgid %d\n",
200 cap_sys_admin, cap_setuid, cap_setgid);
201
202 // Not required as mount/umount uses fork(), then seteuid(0)
203 if( 0 > ::prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) ) { ERR_PRINT("prctl() failed"); }
204
205 ::cap_free(caps);
206
207 if( !cap_sys_admin || !cap_setuid || !cap_setgid ) {
208 ERR_PRINT("capabilities incomplete, needs: cap_sys_admin, cap_setuid, cap_setgid, uid is % " PRIu32 "", caller_uid);
209 return;
210 }
211
212 if( !set_groups(sizeof(group_list)/sizeof(*group_list), group_list) ) {
213 return;
214 }
215
216 } else {
217 print_creds("root level - setuid root -> user");
218
219 if constexpr ( change_caps ) {
220 ::cap_t caps;
221 caps = ::cap_get_proc();
222 if( nullptr == caps ) {
223 ERR_PRINT("cap_get_proc() failed");
224 return;
225 }
226
227 if( !cap_set_flag(caps, ::CAP_EFFECTIVE, cap_list_size, cap_list, ::CAP_SET) ) { return; }
228 if( !cap_set_flag(caps, ::CAP_INHERITABLE, cap_list_size, cap_list, ::CAP_SET) ) { return; }
229 if( !cap_set_flag(caps, ::CAP_PERMITTED, cap_list_size, cap_list, ::CAP_SET) ) { return; }
230 if( !cap_set_proc(caps) ) { return; }
231 {
232 char* c_str = ::cap_to_text(caps, nullptr);
233 jau::fprintf_td(stderr, "root level: caps %s\n", c_str);
234 ::cap_free(c_str);
235 }
236 if( 0 > ::prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) ) {
237 ERR_PRINT("prctl() failed");
238 }
239 ::cap_free(caps);
240 } else {
241 jau::fprintf_td(stderr, "using: changing caps disabled\n");
242 }
243
244 if( !set_groups(sizeof(group_list)/sizeof(*group_list), group_list) ) {
245 return;
246 }
247 if( !set_effective_gid(group_id) ) {
248 return;
249 }
250 if( !set_effective_uid(user_id) ) {
251 return;
252 }
253
254 if constexpr ( change_caps ) {
255 if( !cap_set_proc_flag("user level", CAP_EFFECTIVE, cap_list_size, cap_list) ) { return; }
256 }
257 }
258 print_creds("user level");
259 REQUIRE( user_id == ::geteuid() );
260
261 {
263 REQUIRE( true == image_stats.exists() );
264
265 const std::string mount_point = temp_root+"_mount";
266 jau::fs::remove(mount_point, jau::fs::traverse_options::recursive); // start fresh
267 REQUIRE( true == jau::fs::mkdir(mount_point, jau::fs::fmode_t::def_dir_prot) );
268
270 {
271 REQUIRE( user_id == ::geteuid() );
272 print_creds("pre-mount");
273 print_caps("pre-mount");
274
275 jau::fs::mountflags_t flags = 0;
276#ifdef __linux__
278#endif
279 jau::fprintf_td(stderr, "MountFlags %" PRIu64 "\n", flags);
280 mctx = jau::fs::mount_image(image_stats.path(), mount_point, "squashfs", flags, "");
281
282 print_creds("post-mount");
283 print_caps("post-mount");
284 REQUIRE( user_id == ::geteuid() );
285 }
286 REQUIRE( true == mctx.mounted );
287
292 const std::string root_copy = temp_root+"_copy_test50";
294 testxx_copy_r_p("test50_mount_copy_r_p", mount_point, 1 /* source_added_dead_links */, root_copy, copts, false /* dest_is_vfat */);
295 REQUIRE( true == jau::fs::remove(root_copy, jau::fs::traverse_options::recursive) );
296
297 bool umount_ok;
298 {
299 REQUIRE( user_id == ::geteuid() );
300 print_creds("pre-umount");
301 print_caps("pre-umount");
302
303 jau::fs::umountflags_t flags = 0;
304#ifdef __linux__
305 flags |= jau::fs::umountflags_linux::detach; // lazy
306#endif
307 jau::fprintf_td(stderr, "UnmountFlags %d\n", flags);
308 umount_ok = jau::fs::umount(mctx, flags);
309
310 print_creds("post-umount");
311 print_caps("post-umount");
312 REQUIRE( user_id == ::geteuid() );
313 }
314 REQUIRE( true == umount_ok );
315
316 if constexpr ( _remove_target_test_dir ) {
317 REQUIRE( true == jau::fs::remove(mount_point, jau::fs::traverse_options::recursive) );
318 }
319 }
320 }
321};
322
323METHOD_AS_TEST_CASE( TestFileUtil02::test50_mount_copy_r_p, "Test TestFileUtil02 - test50_mount_copy_r_p");
#define INFO_STR(msg)
Definition: catch2_ext.hpp:75
std::string executable_path
The main argv[0] test executable path.
static bool cap_get_flag(cap_t cap_p, cap_value_t cap, cap_flag_t flag, cap_flag_value_t *value_p) noexcept
::gid_t get_gid(const std::string &groupname)
Get group-id by groupname using system commands getent and cut.
static bool cap_set_proc_flag(const std::string &title, cap_flag_t flag, int ncap, const cap_value_t *cap_list) noexcept
static void print_caps(const std::string &title)
static bool cap_set_flag(cap_t cap_p, cap_flag_t flag, int ncap, const cap_value_t *caps, cap_flag_value_t value) noexcept
jau::fs::file_stats getTestDataImageFile(const std::string &test_exe_path) noexcept
const std::string temp_root
Platform agnostic representation of POSIX ::lstat() and ::stat() for a given pathname.
Definition: file_util.hpp:406
constexpr bool exists() const noexcept
Returns true if entity does not exist, exclusive bit.
Definition: file_util.hpp:648
std::string path() const noexcept
Returns the unix path representation.
Definition: file_util.hpp:530
User account information of the underlying OS.
Definition: user_info.hpp:42
id_t gid() const noexcept
Definition: user_info.hpp:79
bool isValid() const noexcept
Definition: user_info.hpp:77
#define ERR_PRINT(...)
Use for unconditional error messages, prefix '[elapsed_time] Error @ FILE:LINE FUNC: '.
Definition: debug.hpp:109
bool mkdir(const std::string &path, const fmode_t mode=jau::fs::fmode_t::def_dir_prot, const bool verbose=false) noexcept
Create directory.
Definition: file_util.cpp:909
uint64_t mountflags_t
Generic flag bit values for mount() flags.
Definition: file_util.hpp:1191
mount_ctx mount_image(const std::string &image_path, const std::string &target, const std::string &fs_type, const mountflags_t flags, const std::string &fs_options="")
Attach the filesystem image named in image_path to target using an intermediate platform specific fil...
Definition: file_util.cpp:1944
int umountflags_t
Generic flag bit values for umount() flags.
Definition: file_util.hpp:1328
copy_options
Filesystem copy options used to copy() path elements.
Definition: file_util.hpp:1035
bool remove(const std::string &path, const traverse_options topts=traverse_options::none) noexcept
Remove the given path.
Definition: file_util.cpp:1173
bool umount(const mount_ctx &context, const umountflags_t flags)
Detach the given mount_ctx context
Definition: file_util.cpp:2147
@ def_dir_prot
Default directory protection bit: Safe default: POSIX S_IRWXU | S_IRGRP | S_IXGRP or rwx_usr | read_g...
@ verbose
Enable verbosity mode, show error messages on stderr.
@ sync
Ensure data and meta-data file synchronization is performed via ::fsync() after asynchronous copy ope...
@ preserve_all
Preserve uid and gid if allowed and access- and modification-timestamps, i.e.
@ recursive
Traverse through directories, i.e.
@ recursive
Traverse through directories, i.e.
void PLAIN_PRINT(const bool printPrefix, const char *format,...) noexcept
Use for unconditional plain messages, prefix '[elapsed_time] ' if printPrefix == true.
Definition: debug.cpp:258
int fprintf_td(const uint64_t elapsed_ms, FILE *stream, const char *format,...) noexcept
Convenient fprintf() invocation, prepending the given elapsed_ms timestamp.
Definition: debug.cpp:270
static constexpr const bool _remove_target_test_dir
void testxx_copy_r_p(const std::string &title, const jau::fs::file_stats &source, const int source_added_dead_links, const std::string &dest, const jau::fs::copy_options copts, const bool dest_is_vfat)
METHOD_AS_TEST_CASE(TestFileUtil02::test50_mount_copy_r_p, "Test TestFileUtil02 - test50_mount_copy_r_p")