jaulib v1.4.1
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::io::fs::file_stats image_stats = getTestDataImageFile(executable_path);
264 REQUIRE( true == image_stats.exists() );
265
266 const std::string mount_point = temp_root+"_mount";
268 REQUIRE(true == jau::io::fs::mkdir(mount_point, jau::io::fs::fmode_t::def_dir_prot));
269
271 {
272 REQUIRE( user_id == ::geteuid() );
273 print_creds("pre-mount");
274 print_caps("pre-mount");
275
277#ifdef __linux__
279#endif
280 jau::fprintf_td(stderr, "MountFlags %" PRIu64 "\n", flags);
281 mctx = jau::io::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 */);
297
298 bool umount_ok;
299 {
300 REQUIRE( user_id == ::geteuid() );
301 print_creds("pre-umount");
302 print_caps("pre-umount");
303
305#ifdef __linux__
307#endif
308 jau::fprintf_td(stderr, "UnmountFlags %d\n", flags);
309 umount_ok = jau::io::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 ) {
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
const std::string temp_root
jau::io::fs::file_stats getTestDataImageFile(const std::string &test_exe_path) noexcept
Platform agnostic representation of POSIX ::lstat() and ::stat() for a given pathname.
std::string path() const noexcept
Returns the unix path representation.
constexpr bool exists() const noexcept
Returns true if entity does not exist, exclusive bit.
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:122
constexpr std::underlying_type_t< E > number(const E v) noexcept
constexpr bool value(const Bool rhs) noexcept
bool umount(const mount_ctx &context, const umountflags_t flags)
Detach the given mount_ctx context
int umountflags_t
Generic flag bit values for umount() 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...
copy_options
Filesystem copy options used to copy() path elements.
bool mkdir(const std::string &path, const fmode_t mode=fmode_t::def_dir_prot, const bool verbose=false) noexcept
Create directory.
bool remove(const std::string &path, const traverse_options topts=traverse_options::none) noexcept
Remove the given path.
uint64_t mountflags_t
Generic flag bit values for mount() flags.
@ 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:264
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:276
static constexpr const bool _remove_target_test_dir
void testxx_copy_r_p(const std::string &title, const jau::io::fs::file_stats &source, const int source_added_dead_links, const std::string &dest, const jau::io::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")