diff --git a/native/src/include/socket.hpp b/native/src/include/socket.hpp index 486300028..973991a6b 100644 --- a/native/src/include/socket.hpp +++ b/native/src/include/socket.hpp @@ -22,3 +22,17 @@ void write_int_be(int fd, int val); std::string read_string(int fd); bool read_string(int fd, std::string &str); void write_string(int fd, std::string_view str); + +template requires(std::is_trivially_copyable_v) +void write_vector(int fd, const std::vector &vec) { + write_int(fd, static_cast(vec.size())); + xwrite(fd, vec.data(), vec.size() * sizeof(T)); +} + +template requires(std::is_trivially_copyable_v) +bool read_vector(int fd, std::vector &vec) { + int size = read_int(fd); + if (size == -1) return false; + vec.resize(size); + return xread(fd, vec.data(), size * sizeof(T)) == size * sizeof(T); +} diff --git a/native/src/su/connect.cpp b/native/src/su/connect.cpp index 4d01aec71..4d53f2fdb 100644 --- a/native/src/su/connect.cpp +++ b/native/src/su/connect.cpp @@ -166,7 +166,7 @@ void app_log(const su_context &ctx) { vector extras; extras.reserve(6); extras.emplace_back("from.uid", ctx.info->uid); - extras.emplace_back("to.uid", ctx.req.uid); + extras.emplace_back("to.uid", static_cast(ctx.req.uid)); extras.emplace_back("pid", ctx.pid); extras.emplace_back("policy", ctx.info->access.policy); extras.emplace_back("command", get_cmd(ctx.req)); diff --git a/native/src/su/su.cpp b/native/src/su/su.cpp index cc9d480ea..8d5f8a7e8 100644 --- a/native/src/su/su.cpp +++ b/native/src/su/su.cpp @@ -22,12 +22,12 @@ int quit_signals[] = { SIGALRM, SIGABRT, SIGHUP, SIGPIPE, SIGQUIT, SIGTERM, SIGINT, 0 }; -static void usage(int status) { +[[noreturn]] static void usage(int status) { FILE *stream = (status == EXIT_SUCCESS) ? stdout : stderr; fprintf(stream, "MagiskSU\n\n" - "Usage: su [options] [-] [user [argument...]]\n\n" + "Usage: su [options] [-] [user[:group,...] [argument...]]\n\n" "Options:\n" " -c, --command COMMAND pass COMMAND to the invoked shell\n" " -h, --help display this help message and exit\n" @@ -110,7 +110,6 @@ int su_client_main(int argc, char *argv[]) { break; case 'h': usage(EXIT_SUCCESS); - break; case 'l': su_req.login = true; break; @@ -146,12 +145,19 @@ int su_client_main(int argc, char *argv[]) { } /* username or uid */ if (optind < argc) { + std::string_view uidgid = argv[optind]; + auto sep = split_view(uidgid, ":"); + if (sep.size() > 2) usage(EXIT_FAILURE); + auto user = std::string(sep[0]); struct passwd *pw; - pw = getpwnam(argv[optind]); - if (pw) - su_req.uid = pw->pw_uid; - else - su_req.uid = parse_int(argv[optind]); + pw = getpwnam(user.data()); + su_req.uid = pw ? pw->pw_uid : parse_int(user); + if (sep.size() == 2) { + for (auto group : split_view(sep[1], ",")) { + pw = getpwnam(std::string(group).data()); + su_req.gids.push_back(pw ? pw->pw_gid : parse_int(group)); + } + } optind++; } @@ -164,6 +170,7 @@ int su_client_main(int argc, char *argv[]) { xwrite(fd, &su_req, sizeof(su_req_base)); write_string(fd, su_req.shell); write_string(fd, su_req.command); + write_vector(fd, su_req.gids); // Wait for ack from daemon if (read_int(fd)) { diff --git a/native/src/su/su.hpp b/native/src/su/su.hpp index 6e6ddee98..e59288677 100644 --- a/native/src/su/su.hpp +++ b/native/src/su/su.hpp @@ -42,7 +42,7 @@ private: }; struct su_req_base { - int uid = AID_ROOT; + uid_t uid = AID_ROOT; bool login = false; bool keepenv = false; bool mount_master = false; @@ -51,6 +51,7 @@ struct su_req_base { struct su_request : public su_req_base { std::string shell = DEFAULT_SHELL; std::string command; + std::vector gids; }; struct su_context { diff --git a/native/src/su/su_daemon.cpp b/native/src/su/su_daemon.cpp index 4edaf4321..44e09e645 100644 --- a/native/src/su/su_daemon.cpp +++ b/native/src/su/su_daemon.cpp @@ -230,11 +230,20 @@ static shared_ptr get_su_info(unsigned uid) { } // Set effective uid back to root, otherwise setres[ug]id will fail if uid isn't root -static void set_identity(unsigned uid) { +static void set_identity(uid_t uid, const std::vector &groups) { if (seteuid(0)) { PLOGE("seteuid (root)"); } - if (setresgid(uid, uid, uid)) { + gid_t gid; + if (groups.size() > 0) { + if (setgroups(groups.size(), groups.data())) { + PLOGE("setgroups"); + } + gid = groups[0]; + } else { + gid = uid; + } + if (setresgid(gid, gid, gid)) { PLOGE("setresgid (%u)", uid); } if (setresuid(uid, uid, uid)) { @@ -254,7 +263,8 @@ void su_daemon_handler(int client, const sock_cred *cred) { // Read su_request if (xxread(client, &ctx.req, sizeof(su_req_base)) < 0 || !read_string(client, ctx.req.shell) - || !read_string(client, ctx.req.command)) { + || !read_string(client, ctx.req.command) + || !read_vector(client, ctx.req.gids)) { LOGW("su: remote process probably died, abort\n"); ctx.info.reset(); write_int(client, DENY); @@ -443,7 +453,7 @@ void su_daemon_handler(int client, const sock_cred *cred) { sigset_t block_set; sigemptyset(&block_set); sigprocmask(SIG_SETMASK, &block_set, nullptr); - set_identity(ctx.req.uid); + set_identity(ctx.req.uid, ctx.req.gids); execvp(ctx.req.shell.data(), (char **) argv); fprintf(stderr, "Cannot execute %s: %s\n", ctx.req.shell.data(), strerror(errno)); PLOGE("exec");