jaulib v1.3.6
Jau Support Library (C++, Java, ..)
Loading...
Searching...
No Matches
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#include <jau/enum_util.hpp>
29
30extern "C" {
31 #include <unistd.h>
32 #include <grp.h>
33 #include <pwd.h>
34 #include <sys/types.h>
35 #include <sys/mount.h>
36 #include <sys/capability.h>
37 #include <sys/prctl.h>
38}
39
41 private:
42 static constexpr const bool change_caps = false;
43
44 static void print_creds(const std::string& title) {
45 jau::fprintf_td(stderr, "%s: uid %" PRIu32 ", euid %" PRIu32 ", gid %" PRIu32 ", egid %" PRIu32 "\n",
46 title.c_str(), ::getuid(), ::geteuid(), ::getgid(), ::getegid());
47
48 gid_t gid_list[64];
49 int count = ::getgroups(sizeof(gid_list)/sizeof(*gid_list), gid_list);
50 if( 0 > count ) {
51 ERR_PRINT("getgroups() failed");
52 } else {
53 jau::fprintf_td(stderr, "%s: groups[%d]: ", title.c_str(), count);
54 for(int i=0; i<count; ++i) {
55 fprintf(stderr, "%" PRIu32 ", ", gid_list[i]);
56 }
57 fprintf(stderr, "\n");
58 }
59
60 }
61 static bool set_groups(size_t size, const gid_t *list) {
62 if( 0 > ::setgroups(size, list) ) {
63 ERR_PRINT("setgroups failed");
64 return false;
65 }
66 return true;
67 }
68
69 static bool set_effective_gid(::gid_t group_id) {
70 if( 0 != ::setegid(group_id) ) {
71 ERR_PRINT("setegid(%" PRIu32 ") failed", group_id);
72 return false;
73 }
74 return true;
75 }
76
77 static bool set_effective_uid(::uid_t user_id) {
78 if( 0 != ::seteuid(user_id) ) {
79 ERR_PRINT("seteuid(%" PRIu32 ") failed", user_id);
80 return false;
81 }
82 return true;
83 }
84
85 public:
86
87 static bool cap_get_flag(cap_t cap_p, cap_value_t cap, cap_flag_t flag, cap_flag_value_t *value_p) noexcept {
88 if( 0 != ::cap_get_flag(cap_p, cap, flag, value_p) ) {
89 ERR_PRINT("cap_get_flag() failed");
90 return false;
91 }
92 return true;
93 }
94 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 {
95 if( 0 != ::cap_set_flag(cap_p, flag, ncap, caps, value) ) {
96 ERR_PRINT("cap_set_flag() failed");
97 return false;
98 }
99 return true;
100 }
101
102 static bool cap_set_proc_flag(const std::string& title, cap_flag_t flag, int ncap, const cap_value_t *cap_list) noexcept {
103 ::cap_t cap_p;
104 cap_p = ::cap_get_proc();
105 if( nullptr == cap_p ) {
106 ERR_PRINT("cap_get_proc() failed");
107 return false;
108 }
109 if( !cap_set_flag(cap_p, flag, ncap, cap_list, CAP_SET) ) { return false; }
110 ::cap_set_proc(cap_p);
111 {
112 char* c_str = ::cap_to_text(cap_p, nullptr);
113 jau::fprintf_td(stderr, "%s: set caps %s\n", title.c_str(), c_str);
114 ::cap_free(c_str);
115 }
116 ::cap_free(cap_p);
117 return true;
118 }
119
120 static void print_caps(const std::string& title) {
121 ::cap_t caps;
122 caps = ::cap_get_proc();
123 if( nullptr == caps ) {
124 ERR_PRINT("cap_get_proc() failed");
125 return;
126 }
127 {
128 char* c_str = ::cap_to_text(caps, nullptr);
129 jau::fprintf_td(stderr, "%s: caps %s\n", title.c_str(), c_str);
130 ::cap_free(c_str);
131 }
132 ::cap_free(caps);
133 }
134
135 /**
136 * Get group-id by groupname using system commands `getent` and `cut`.
137 *
138 * Known group-ids are
139 * - Ubuntu, Debian group 24: cdrom
140 * - FreeBSD, Ubuntu, Debian group 44: video
141 * - Alpine/Linux group 27: video
142 */
143 static ::gid_t get_gid(const std::string& groupname) {
144 static const ::gid_t default_group = 44;
145 std::string cmd("getent group "+groupname+" | cut -d: -f3");
146 FILE* fp = ::popen(cmd.c_str(), "r");
147 if (fp == nullptr) {
148 fprintf(stderr, "Command failed (1) '%s'\n", cmd.c_str() );
149 return default_group;
150 }
151 char result[100];
152 ::gid_t result_int = default_group;
153 if( nullptr != ::fgets(result, sizeof(result), fp) ) {
154 result_int = static_cast<::gid_t>( ::atoi(result) );
155 jau::PLAIN_PRINT(true, "get_gid(%s) -> %s (%d)", groupname.c_str(), result, (int)result_int);
156 } else {
157 fprintf(stderr, "Command failed (2) '%s'\n", cmd.c_str() );
158 }
159 ::pclose(fp);
160 return result_int;
161 }
162
164 INFO_STR("\n\ntest50_mount_copy_r_p\n");
165 // ::cap_value_t cap_list[] = { CAP_SYS_ADMIN, CAP_SETUID, CAP_SETGID, CAP_CHOWN, CAP_FOWNER };
166 ::cap_value_t cap_list[] = { CAP_SYS_ADMIN, CAP_SETUID, CAP_SETGID };
167 const size_t cap_list_size = sizeof(cap_list) / sizeof(*cap_list);
168
169 const ::uid_t super_uid = 0;
170 ::uid_t caller_uid = ::getuid();
171
172 ::uid_t user_id = caller_uid;
173 jau::os::UserInfo user_info(user_id);
174 if( !user_info.isValid() ) {
175 ERR_PRINT("couldn't fetch [SUDO_]UID");
176 return;
177 }
178 ::gid_t group_id = (::gid_t)user_info.gid();
179 ::gid_t group_list[] = { user_id, group_id, get_gid("video") };
180
181 const bool setuid_user_to_root = super_uid != caller_uid;
182 if( setuid_user_to_root ) {
183 print_creds("user level - setuid user -> root");
184
185 ::cap_t caps;
186 caps = ::cap_get_proc();
187 if( nullptr == caps ) {
188 ERR_PRINT("cap_get_proc() failed");
189 return;
190 }
191 {
192 char* c_str = ::cap_to_text(caps, nullptr);
193 jau::fprintf_td(stderr, "user level: caps %s\n", c_str);
194 ::cap_free(c_str);
195 }
196 cap_flag_value_t cap_sys_admin, cap_setuid, cap_setgid;
197 if( !cap_get_flag(caps, CAP_SYS_ADMIN, ::CAP_EFFECTIVE, &cap_sys_admin) ) { return; }
198 if( !cap_get_flag(caps, CAP_SETUID, ::CAP_EFFECTIVE, &cap_setuid) ) { return; }
199 if( !cap_get_flag(caps, CAP_SETGID, ::CAP_EFFECTIVE, &cap_setgid) ) { return; }
200 jau::fprintf_td(stderr, "Caps: sys_admin %d, setuid %d, setgid %d\n",
201 cap_sys_admin, cap_setuid, cap_setgid);
202
203 // Not required as mount/umount uses fork(), then seteuid(0)
204 if( 0 > ::prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) ) { ERR_PRINT("prctl() failed"); }
205
206 ::cap_free(caps);
207
208 if( !cap_sys_admin || !cap_setuid || !cap_setgid ) {
209 ERR_PRINT("capabilities incomplete, needs: cap_sys_admin, cap_setuid, cap_setgid, uid is % " PRIu32 "", caller_uid);
210 return;
211 }
212
213 if( !set_groups(sizeof(group_list)/sizeof(*group_list), group_list) ) {
214 return;
215 }
216
217 } else {
218 print_creds("root level - setuid root -> user");
219
220 if constexpr ( change_caps ) {
221 ::cap_t caps;
222 caps = ::cap_get_proc();
223 if( nullptr == caps ) {
224 ERR_PRINT("cap_get_proc() failed");
225 return;
226 }
227
228 if( !cap_set_flag(caps, ::CAP_EFFECTIVE, cap_list_size, cap_list, ::CAP_SET) ) { return; }
229 if( !cap_set_flag(caps, ::CAP_INHERITABLE, cap_list_size, cap_list, ::CAP_SET) ) { return; }
230 if( !cap_set_flag(caps, ::CAP_PERMITTED, cap_list_size, cap_list, ::CAP_SET) ) { return; }
231 if( !cap_set_proc(caps) ) { return; }
232 {
233 char* c_str = ::cap_to_text(caps, nullptr);
234 jau::fprintf_td(stderr, "root level: caps %s\n", c_str);
235 ::cap_free(c_str);
236 }
237 if( 0 > ::prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) ) {
238 ERR_PRINT("prctl() failed");
239 }
240 ::cap_free(caps);
241 } else {
242 jau::fprintf_td(stderr, "using: changing caps disabled\n");
243 }
244
245 if( !set_groups(sizeof(group_list)/sizeof(*group_list), group_list) ) {
246 return;
247 }
248 if( !set_effective_gid(group_id) ) {
249 return;
250 }
251 if( !set_effective_uid(user_id) ) {
252 return;
253 }
254
255 if constexpr ( change_caps ) {
256 if( !cap_set_proc_flag("user level", CAP_EFFECTIVE, cap_list_size, cap_list) ) { return; }
257 }
258 }
259 print_creds("user level");
260 REQUIRE( user_id == ::geteuid() );
261
262 {
263 jau::fs::file_stats image_stats = getTestDataImageFile(executable_path);
264 REQUIRE( true == image_stats.exists() );
265
266 const std::string mount_point = temp_root+"_mount";
267 jau::fs::remove(mount_point, jau::fs::traverse_options::recursive); // start fresh
268 REQUIRE( true == jau::fs::mkdir(mount_point, jau::fs::fmode_t::def_dir_prot) );
269
271 {
272 REQUIRE( user_id == ::geteuid() );
273 print_creds("pre-mount");
274 print_caps("pre-mount");
275
276 jau::fs::mountflags_t flags = 0;
277#ifdef __linux__
279#endif
280 jau::fprintf_td(stderr, "MountFlags %" PRIu64 "\n", flags);
281 mctx = jau::fs::mount_image(image_stats.path(), mount_point, "squashfs", flags, "");
282
283 print_creds("post-mount");
284 print_caps("post-mount");
285 REQUIRE( user_id == ::geteuid() );
286 }
287 REQUIRE( true == mctx.mounted );
288
293 const std::string root_copy = temp_root+"_copy_test50";
295 testxx_copy_r_p("test50_mount_copy_r_p", mount_point, 1 /* source_added_dead_links */, root_copy, copts, false /* dest_is_vfat */);
296 REQUIRE( true == jau::fs::remove(root_copy, jau::fs::traverse_options::recursive) );
297
298 bool umount_ok;
299 {
300 REQUIRE( user_id == ::geteuid() );
301 print_creds("pre-umount");
302 print_caps("pre-umount");
303
304 jau::fs::umountflags_t flags = 0;
305#ifdef __linux__
307#endif
308 jau::fprintf_td(stderr, "UnmountFlags %d\n", flags);
309 umount_ok = jau::fs::umount(mctx, flags);
310
311 print_creds("post-umount");
312 print_caps("post-umount");
313 REQUIRE( user_id == ::geteuid() );
314 }
315 REQUIRE( true == umount_ok );
316
317 if constexpr ( _remove_target_test_dir ) {
318 REQUIRE( true == jau::fs::remove(mount_point, jau::fs::traverse_options::recursive) );
319 }
320 }
321 }
322};
323
324METHOD_AS_TEST_CASE( TestFileUtil02::test50_mount_copy_r_p, "Test TestFileUtil02 - test50_mount_copy_r_p");
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.
constexpr bool exists() const noexcept
Returns true if entity does not exist, exclusive bit.
std::string path() const noexcept
Returns the unix path representation.
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
constexpr std::underlying_type_t< E > number(const E v) noexcept
constexpr bool value(const Bool rhs) noexcept
bool mkdir(const std::string &path, const fmode_t mode=jau::fs::fmode_t::def_dir_prot, const bool verbose=false) noexcept
Create directory.
uint64_t mountflags_t
Generic flag bit values for mount() flags.
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...
int umountflags_t
Generic flag bit values for umount() flags.
copy_options
Filesystem copy options used to copy() path elements.
bool remove(const std::string &path, const traverse_options topts=traverse_options::none) noexcept
Remove the given path.
bool umount(const mount_ctx &context, const umountflags_t flags)
Detach the given mount_ctx context
@ 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")