├── .gitignore ├── LICENSE ├── Makefile ├── Makefile.local.sample ├── README.md ├── include ├── pts.h ├── su.h └── utils.h ├── init.sud.rc └── src ├── daemon.c ├── pts.c └── su.c /.gitignore: -------------------------------------------------------------------------------- 1 | bin/* 2 | src/*.o 3 | Makefile.local -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020, Corellium, LLC 2 | Copyright 2013, Tan Chee Eng (@tan-ce) 3 | Copyright 2010, Adam Shanks (@ChainsDD) 4 | Copyright 2008, Zinx Verituse (@zinxv) 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | -include Makefile.local 2 | 3 | CFLAGS := -O2 -Wall -Wextra 4 | CFLAGS += -I./include/ 5 | LDFLAGS := -llog 6 | 7 | BIN_DIR := bin 8 | SRC_DIR := src 9 | 10 | OBJS = 11 | OBJS += $(sort $(patsubst %.c,%.o,$(wildcard $(SRC_DIR)/*.c))) 12 | 13 | .PHONY: all clean 14 | 15 | all: su 16 | 17 | clean: 18 | /bin/rm --force $(OBJS) 19 | /bin/rm --force $(BIN_DIR)/su 20 | 21 | su: $(OBJS) 22 | $(CC) -o $(BIN_DIR)/$@ $^ $(LDFLAGS) $(EXTRA_LDFLAGS) 23 | 24 | %.o: %.c 25 | $(CC) -o $@ $^ -c $(CFLAGS) $(EXTRA_CFLAGS) 26 | -------------------------------------------------------------------------------- /Makefile.local.sample: -------------------------------------------------------------------------------- 1 | export CC := ~/Android/Sdk/ndk/20.0.5594570/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang 2 | export SYSROOT := ~/Android/Sdk/ndk/20.0.5594570/platforms/android-24/arch-arm64 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sud(aemon) 2 | 3 | ## What is this? 4 | 5 | `sud` is a simplistic `su` daemon for Corellium, and maybe other, Android devices. It is older [Superuser](https://github.com/koush/Superuser) code, slightly refactored and heavily stripped down to the pure basics. Simply put, it will allow anyone who requests for `su` to be granted it. This is *not* something you should run on your personal device, this is a huge security issue. However if you want to test things or allow anything to run with `root` privledges, this is for you. This binary could also be easily modified to log all incoming `su` requests for such things as simplistic "taint" trace or watching what binaries use it for. 6 | 7 | ### Build 8 | 9 | Building should be rather simple, simply create a `Makefile.local` file to properly point to you compiled of choice (`CC`) and system root (`SYSROOT`). These are located inside your Android NDK (`ANDROID_NDK`) directory for most people. You can check the `Makefile.local.sample` in this repository as an example. Once this is done, simply run `make`. 10 | 11 | ``` 12 | diff@larry:../sud/ $ make 13 | ~/Android/Sdk/ndk/20.0.5594570/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang -o src/daemon.o src/daemon.c -c -O2 -Wall -Wextra -I./include/ 14 | ~/Android/Sdk/ndk/20.0.5594570/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang -o src/pts.o src/pts.c -c -O2 -Wall -Wextra -I./include/ 15 | ~/Android/Sdk/ndk/20.0.5594570/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang -o src/su.o src/su.c -c -O2 -Wall -Wextra -I./include/ 16 | ~/Android/Sdk/ndk/20.0.5594570/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang -o bin/su src/daemon.o src/pts.o src/su.o -llog 17 | diff@larry:../sud/ $ ls -l bin 18 | total 44 19 | -rwxrwxr-x 1 diff diff 41416 Jan 21 13:31 su 20 | ``` 21 | 22 | Alternatively, check the releases tab for a precompiled `su` binary. 23 | 24 | ### Installing on Corellium Android Devices 25 | 26 | Build the binaries in this repository following the directions in the `Build` section. Then on 27 | the local machines connect to the device via `adb` and push the following files; 28 | 29 | ``` 30 | adb push init.sud.rc /data/local/tmp/ 31 | adb push bin/su /data/local/tmp/ 32 | ``` 33 | 34 | Then get a `shell`, via `adb` and run the following commands; 35 | 36 | ``` 37 | su 38 | /system/bin/mount -orw,remount /system 39 | /vendor/bin/cp /data/local/tmp/su /system/xbin/su 40 | /vendor/bin/chown root /system/xbin/su 41 | /system/bin/chcon u:object_r:su_exec:s0 /system/xbin/su 42 | /vendor/bin/chmod 06755 /system/xbin/su 43 | 44 | /vendor/bin/cp /data/local/tmp/init.sud.rc /system/etc/init/init.sud.rc 45 | /vendor/bin/chown root /system/etc/init/init.sud.rc 46 | /system/bin/chcon u:object_r:system_file:s0 /system/etc/init/init.sud.rc 47 | /vendor/bin/chmod 644 /system/etc/init/init.sud.rc 48 | 49 | /system/bin/reboot 50 | ``` 51 | 52 | Note for Android 12 - there is no `/system` mount, so you will need to remount the root mount `/` instead. So replace the above mount with `/system/bin/mount -orw,remount /` 53 | 54 | ## License 55 | 56 | These files are a mash-up and refactor from a number of sources. We've retained both the 57 | licensing from all those files and added ourselves to any files modified. If we've somehow 58 | missed anyone from the files in the transition, please submit a pull request and we will 59 | fix it as soon as possible! 60 | 61 | ``` 62 | Copyright 2020, Corellium, LLC 63 | Copyright 2013, Tan Chee Eng (@tan-ce) 64 | Copyright 2010, Adam Shanks (@ChainsDD) 65 | Copyright 2008, Zinx Verituse (@zinxv) 66 | 67 | Licensed under the Apache License, Version 2.0 (the "License"); 68 | you may not use this file except in compliance with the License. 69 | You may obtain a copy of the License at 70 | 71 | http://www.apache.org/licenses/LICENSE-2.0 72 | 73 | Unless required by applicable law or agreed to in writing, software 74 | distributed under the License is distributed on an "AS IS" BASIS, 75 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 76 | See the License for the specific language governing permissions and 77 | limitations under the License. 78 | ``` 79 | -------------------------------------------------------------------------------- /include/pts.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013, Tan Chee Eng (@tan-ce) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | * pts.h 19 | * 20 | * Manages the pseudo-terminal driver on Linux/Android and provides some 21 | * helper functions to handle raw input mode and terminal window resizing 22 | */ 23 | 24 | #ifndef _PTS_H_ 25 | #define _PTS_H_ 26 | 27 | /** 28 | * pts_open 29 | * 30 | * Opens a pts device and returns the name of the slave tty device. 31 | * 32 | * Arguments 33 | * slave_name the name of the slave device 34 | * slave_name_size the size of the buffer passed via slave_name 35 | * 36 | * Return Values 37 | * on failure either -2 or -1 (errno set) is returned. 38 | * on success, the file descriptor of the master device is returned. 39 | */ 40 | int pts_open(char *slave_name, size_t slave_name_size); 41 | 42 | /** 43 | * set_stdin_raw 44 | * 45 | * Changes stdin to raw unbuffered mode, disables echo, 46 | * auto carriage return, etc. 47 | * 48 | * Return Value 49 | * on failure -1, and errno is set 50 | * on success 0 51 | */ 52 | int set_stdin_raw(void); 53 | 54 | /** 55 | * restore_stdin 56 | * 57 | * Restore termios on stdin to the state it was before 58 | * set_stdin_raw() was called. If set_stdin_raw() was 59 | * never called, does nothing and doesn't return an error. 60 | * 61 | * This function is async-safe. 62 | * 63 | * Return Value 64 | * on failure, -1 and errno is set 65 | * on success, 0 66 | */ 67 | int restore_stdin(void); 68 | 69 | /** 70 | * watch_sigwinch_async 71 | * 72 | * After calling this function, if the application receives 73 | * SIGWINCH, the terminal window size will be read from 74 | * "input" and set on "output". 75 | * 76 | * NOTE: This function blocks SIGWINCH and spawns a thread. 77 | * 78 | * Arguments 79 | * master A file descriptor of the TTY window size to follow 80 | * slave A file descriptor of the TTY window size which is 81 | * to be set on SIGWINCH 82 | * 83 | * Return Value 84 | * on failure, -1 and errno will be set. In this case, no 85 | * thread has been spawned and SIGWINCH will not be 86 | * blocked. 87 | * on success, 0 88 | */ 89 | int watch_sigwinch_async(int master, int slave); 90 | 91 | /** 92 | * watch_sigwinch_cleanup 93 | * 94 | * Cause the SIGWINCH watcher thread to terminate 95 | */ 96 | void watch_sigwinch_cleanup(void); 97 | 98 | /** 99 | * pump_stdin_async 100 | * 101 | * Forward data from STDIN to the given FD 102 | * in a seperate thread 103 | */ 104 | void pump_stdin_async(int outfd); 105 | 106 | /** 107 | * pump_stdout_blocking 108 | * 109 | * Forward data from the FD to STDOUT. 110 | * Returns when the remote end of the FD closes. 111 | * 112 | * Before returning, restores stdin settings. 113 | */ 114 | void pump_stdout_blocking(int infd); 115 | 116 | #endif 117 | -------------------------------------------------------------------------------- /include/su.h: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright 2021, Corellium, LLC 3 | ** Copyright 2010, Adam Shanks (@ChainsDD) 4 | ** Copyright 2008, Zinx Verituse (@zinxv) 5 | ** 6 | ** Licensed under the Apache License, Version 2.0 (the "License"); 7 | ** you may not use this file except in compliance with the License. 8 | ** You may obtain a copy of the License at 9 | ** 10 | ** http://www.apache.org/licenses/LICENSE-2.0 11 | ** 12 | ** Unless required by applicable law or agreed to in writing, software 13 | ** distributed under the License is distributed on an "AS IS" BASIS, 14 | ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | ** See the License for the specific language governing permissions and 16 | ** limitations under the License. 17 | */ 18 | 19 | #ifndef SU_h 20 | #define SU_h 1 21 | 22 | #include 23 | 24 | #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) 25 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) 26 | #define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) 27 | #define LOGW LOGD 28 | 29 | #ifdef LOG_TAG 30 | #undef LOG_TAG 31 | #endif 32 | #define LOG_TAG "sud" 33 | 34 | #ifndef AID_SHELL 35 | #define AID_SHELL (get_shell_uid()) 36 | #endif 37 | 38 | #ifndef AID_ROOT 39 | #define AID_ROOT 0 40 | #endif 41 | 42 | #ifndef AID_SYSTEM 43 | #define AID_SYSTEM (get_system_uid()) 44 | #endif 45 | 46 | #ifndef AID_RADIO 47 | #define AID_RADIO (get_radio_uid()) 48 | #endif 49 | 50 | // CyanogenMod-specific behavior 51 | #define CM_ROOT_ACCESS_DISABLED 0 52 | #define CM_ROOT_ACCESS_APPS_ONLY 1 53 | #define CM_ROOT_ACCESS_ADB_ONLY 2 54 | #define CM_ROOT_ACCESS_APPS_AND_ADB 3 55 | 56 | // DO NOT CHANGE LINE BELOW, java package name will always be the same 57 | #define JAVA_PACKAGE_NAME "com.koushikdutta.superuser" 58 | 59 | // If --rename-manifest-package is used in AAPT, this 60 | // must be changed to correspond to the new APK package name 61 | // See the two Android.mk files for more details. 62 | #ifndef REQUESTOR 63 | #define REQUESTOR JAVA_PACKAGE_NAME 64 | #endif 65 | // This is used if wrapping the fragment classes and activities 66 | // with classes in another package. CM requirement. 67 | #ifndef REQUESTOR_PREFIX 68 | #define REQUESTOR_PREFIX JAVA_PACKAGE_NAME 69 | #endif 70 | #define REQUESTOR_DATA_PATH "/data/data/" 71 | #define REQUESTOR_FILES_PATH REQUESTOR_DATA_PATH REQUESTOR "/files" 72 | #define REQUESTOR_USER_PATH "/data/user/" 73 | #define REQUESTOR_CACHE_PATH "/dev/" REQUESTOR 74 | #define REQUESTOR_DAEMON_PATH REQUESTOR_CACHE_PATH ".daemon" 75 | 76 | // there's no guarantee that the db or files are actually created named as such by 77 | // SQLiteOpenHelper, etc. Though that is the behavior as of current. 78 | // it is up to the Android application to symlink as appropriate. 79 | #define REQUESTOR_DATABASE_PATH REQUESTOR "/databases/su.sqlite" 80 | #define REQUESTOR_MULTIUSER_MODE REQUESTOR_FILES_PATH "/multiuser_mode" 81 | 82 | #define DEFAULT_SHELL "/system/bin/sh" 83 | 84 | #define xstr(a) str(a) 85 | #define str(a) #a 86 | 87 | #ifndef VERSION_CODE 88 | #define VERSION_CODE 16 89 | #endif 90 | #define VERSION xstr(VERSION_CODE) " " REQUESTOR 91 | 92 | #define PROTO_VERSION 1 93 | 94 | struct su_initiator { 95 | pid_t pid; 96 | unsigned uid; 97 | unsigned user; 98 | char name[64]; 99 | char bin[PATH_MAX]; 100 | char args[4096]; 101 | }; 102 | 103 | struct su_request { 104 | unsigned uid; 105 | char name[64]; 106 | int login; 107 | int keepenv; 108 | char *shell; 109 | char *command; 110 | char **argv; 111 | int argc; 112 | int optind; 113 | }; 114 | 115 | struct su_user_info { 116 | // the user in android userspace (multiuser) 117 | // that invoked this action. 118 | unsigned android_user_id; 119 | }; 120 | 121 | struct su_context { 122 | struct su_initiator from; 123 | struct su_request to; 124 | struct su_user_info user; 125 | mode_t umask; 126 | char sock_path[PATH_MAX]; 127 | }; 128 | 129 | static inline char *get_command(const struct su_request *to) 130 | { 131 | if (to->command) 132 | return to->command; 133 | if (to->shell) 134 | return to->shell; 135 | char* ret = to->argv[to->optind]; 136 | if (ret) 137 | return ret; 138 | return DEFAULT_SHELL; 139 | } 140 | 141 | int run_daemon(); 142 | int connect_daemon(int argc, char *argv[], int ppid); 143 | int su_main(int argc, char *argv[], int need_client); 144 | // for when you give zero fucks about the state of the child process. 145 | // this version of fork understands you don't care about the child. 146 | // deadbeat dad fork. 147 | int fork_zero_fucks(); 148 | 149 | #include 150 | #include 151 | #define PLOGE(fmt,args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno)) 152 | #define PLOGEV(fmt,err,args...) LOGE(fmt " failed with %d: %s", ##args, err, strerror(err)) 153 | 154 | #endif 155 | -------------------------------------------------------------------------------- /include/utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright 2012, The CyanogenMod Project 3 | ** 4 | ** Licensed under the Apache License, Version 2.0 (the "License"); 5 | ** you may not use this file except in compliance with the License. 6 | ** You may obtain a copy of the License at 7 | ** 8 | ** http://www.apache.org/licenses/LICENSE-2.0 9 | ** 10 | ** Unless required by applicable law or agreed to in writing, software 11 | ** distributed under the License is distributed on an "AS IS" BASIS, 12 | ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | ** See the License for the specific language governing permissions and 14 | ** limitations under the License. 15 | */ 16 | 17 | #ifndef _UTILS_H_ 18 | #define _UTILS_H_ 19 | 20 | #ifndef PROPERTY_VALUE_MAX 21 | #define PROPERTY_VALUE_MAX 92 22 | #endif 23 | 24 | /* reads a file, making sure it is terminated with \n \0 */ 25 | extern char* read_file(const char *fn); 26 | 27 | extern int get_property(const char *data, char *found, const char *searchkey, 28 | const char *not_found); 29 | extern int check_property(const char *data, const char *prefix); 30 | #endif 31 | -------------------------------------------------------------------------------- /init.sud.rc: -------------------------------------------------------------------------------- 1 | # su daemon 2 | service su_daemon /system/xbin/su --daemon 3 | group root 4 | seclabel u:r:su:s0 5 | user root 6 | 7 | on post-fs-data 8 | start su_daemon -------------------------------------------------------------------------------- /src/daemon.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright 2021, Corellium, LLC 3 | ** Copyright 2010, Adam Shanks (@ChainsDD) 4 | ** Copyright 2008, Zinx Verituse (@zinxv) 5 | ** 6 | ** Licensed under the Apache License, Version 2.0 (the "License"); 7 | ** you may not use this file except in compliance with the License. 8 | ** You may obtain a copy of the License at 9 | ** 10 | ** http://www.apache.org/licenses/LICENSE-2.0 11 | ** 12 | ** Unless required by applicable law or agreed to in writing, software 13 | ** distributed under the License is distributed on an "AS IS" BASIS, 14 | ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | ** See the License for the specific language governing permissions and 16 | ** limitations under the License. 17 | */ 18 | 19 | #define _GNU_SOURCE /* for unshare() */ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | #include "su.h" 46 | #include "utils.h" 47 | #include "pts.h" 48 | 49 | int is_daemon = 0; 50 | int daemon_from_uid = 0; 51 | int daemon_from_pid = 0; 52 | 53 | // Constants for the atty bitfield 54 | #define ATTY_IN 1 55 | #define ATTY_OUT 2 56 | #define ATTY_ERR 4 57 | 58 | /* 59 | * Receive a file descriptor from a Unix socket. 60 | * Contributed by @mkasick 61 | * 62 | * Returns the file descriptor on success, or -1 if a file 63 | * descriptor was not actually included in the message 64 | * 65 | * On error the function terminates by calling exit(-1) 66 | */ 67 | static int recv_fd(int sockfd) { 68 | // Need to receive data from the message, otherwise don't care about it. 69 | char iovbuf; 70 | 71 | struct iovec iov = { 72 | .iov_base = &iovbuf, 73 | .iov_len = 1, 74 | }; 75 | 76 | char cmsgbuf[CMSG_SPACE(sizeof(int))]; 77 | 78 | struct msghdr msg = { 79 | .msg_iov = &iov, 80 | .msg_iovlen = 1, 81 | .msg_control = cmsgbuf, 82 | .msg_controllen = sizeof(cmsgbuf), 83 | }; 84 | 85 | if (recvmsg(sockfd, &msg, MSG_WAITALL) != 1) { 86 | goto error; 87 | } 88 | 89 | // Was a control message actually sent? 90 | switch (msg.msg_controllen) { 91 | case 0: 92 | // No, so the file descriptor was closed and won't be used. 93 | return -1; 94 | case sizeof(cmsgbuf): 95 | // Yes, grab the file descriptor from it. 96 | break; 97 | default: 98 | goto error; 99 | } 100 | 101 | struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); 102 | 103 | if (cmsg == NULL || 104 | cmsg->cmsg_len != CMSG_LEN(sizeof(int)) || 105 | cmsg->cmsg_level != SOL_SOCKET || 106 | cmsg->cmsg_type != SCM_RIGHTS) { 107 | error: 108 | LOGE("unable to read fd"); 109 | exit(-1); 110 | } 111 | 112 | return *(int *)CMSG_DATA(cmsg); 113 | } 114 | 115 | /* 116 | * Send a file descriptor through a Unix socket. 117 | * Contributed by @mkasick 118 | * 119 | * On error the function terminates by calling exit(-1) 120 | * 121 | * fd may be -1, in which case the dummy data is sent, 122 | * but no control message with the FD is sent. 123 | */ 124 | static void send_fd(int sockfd, int fd) { 125 | // Need to send some data in the message, this will do. 126 | struct iovec iov = { 127 | .iov_base = "", 128 | .iov_len = 1, 129 | }; 130 | 131 | struct msghdr msg = { 132 | .msg_iov = &iov, 133 | .msg_iovlen = 1, 134 | }; 135 | 136 | char cmsgbuf[CMSG_SPACE(sizeof(int))]; 137 | 138 | if (fd != -1) { 139 | // Is the file descriptor actually open? 140 | if (fcntl(fd, F_GETFD) == -1) { 141 | if (errno != EBADF) { 142 | goto error; 143 | } 144 | // It's closed, don't send a control message or sendmsg will EBADF. 145 | } else { 146 | // It's open, send the file descriptor in a control message. 147 | msg.msg_control = cmsgbuf; 148 | msg.msg_controllen = sizeof(cmsgbuf); 149 | 150 | struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); 151 | 152 | cmsg->cmsg_len = CMSG_LEN(sizeof(int)); 153 | cmsg->cmsg_level = SOL_SOCKET; 154 | cmsg->cmsg_type = SCM_RIGHTS; 155 | 156 | *(int *)CMSG_DATA(cmsg) = fd; 157 | } 158 | } 159 | 160 | if (sendmsg(sockfd, &msg, 0) != 1) { 161 | error: 162 | PLOGE("unable to send fd"); 163 | exit(-1); 164 | } 165 | } 166 | 167 | static int read_int(int fd) { 168 | int val; 169 | int len = read(fd, &val, sizeof(int)); 170 | if (len != sizeof(int)) { 171 | LOGE("unable to read int: %d", len); 172 | exit(-1); 173 | } 174 | return val; 175 | } 176 | 177 | static void write_int(int fd, int val) { 178 | int written = write(fd, &val, sizeof(int)); 179 | if (written != sizeof(int)) { 180 | PLOGE("unable to write int"); 181 | exit(-1); 182 | } 183 | } 184 | 185 | static char* read_string(int fd) { 186 | int len = read_int(fd); 187 | if (len > PATH_MAX || len < 0) { 188 | LOGE("invalid string length %d", len); 189 | exit(-1); 190 | } 191 | char* val = malloc(sizeof(char) * (len + 1)); 192 | if (val == NULL) { 193 | LOGE("unable to malloc string"); 194 | exit(-1); 195 | } 196 | val[len] = '\0'; 197 | int amount = read(fd, val, len); 198 | if (amount != len) { 199 | LOGE("unable to read string"); 200 | exit(-1); 201 | } 202 | return val; 203 | } 204 | 205 | static void write_string(int fd, char* val) { 206 | int len = strlen(val); 207 | write_int(fd, len); 208 | int written = write(fd, val, len); 209 | if (written != len) { 210 | PLOGE("unable to write string"); 211 | exit(-1); 212 | } 213 | } 214 | 215 | static int run_daemon_child(int infd, int outfd, int errfd, int argc, char** argv) { 216 | if (-1 == dup2(outfd, STDOUT_FILENO)) { 217 | PLOGE("dup2 child outfd"); 218 | exit(-1); 219 | } 220 | 221 | if (-1 == dup2(errfd, STDERR_FILENO)) { 222 | PLOGE("dup2 child errfd"); 223 | exit(-1); 224 | } 225 | 226 | if (-1 == dup2(infd, STDIN_FILENO)) { 227 | PLOGE("dup2 child infd"); 228 | exit(-1); 229 | } 230 | 231 | close(infd); 232 | close(outfd); 233 | close(errfd); 234 | 235 | return su_main(argc, argv, 0); 236 | } 237 | 238 | static int daemon_accept(int fd) { 239 | is_daemon = 1; 240 | int pid = read_int(fd); 241 | LOGD("remote pid: %d", pid); 242 | char *pts_slave = read_string(fd); 243 | LOGD("remote pts_slave: %s", pts_slave); 244 | daemon_from_uid = read_int(fd); 245 | LOGD("remote uid: %d", daemon_from_uid); 246 | daemon_from_pid = read_int(fd); 247 | LOGD("remote req pid: %d", daemon_from_pid); 248 | 249 | struct ucred credentials; 250 | int ucred_length = sizeof(struct ucred); 251 | /* fill in the user data structure */ 252 | if(getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &credentials, (unsigned int *)&ucred_length)) { 253 | LOGE("could obtain credentials from unix domain socket"); 254 | exit(-1); 255 | } 256 | // if the credentials on the other side of the wire are NOT root, 257 | // we can't trust anything being sent. 258 | if (credentials.uid != 0) { 259 | daemon_from_uid = credentials.uid; 260 | pid = credentials.pid; 261 | daemon_from_pid = credentials.pid; 262 | } 263 | 264 | // The the FDs for each of the streams 265 | int infd = recv_fd(fd); 266 | int outfd = recv_fd(fd); 267 | int errfd = recv_fd(fd); 268 | 269 | int argc = read_int(fd); 270 | if (argc < 0 || argc > 512) { 271 | LOGE("unable to allocate args: %d", argc); 272 | exit(-1); 273 | } 274 | LOGD("remote args: %d", argc); 275 | char** argv = (char**)malloc(sizeof(char*) * (argc + 1)); 276 | argv[argc] = NULL; 277 | int i; 278 | for (i = 0; i < argc; i++) { 279 | argv[i] = read_string(fd); 280 | } 281 | 282 | // ack 283 | write_int(fd, 1); 284 | 285 | // Fork the child process. The fork has to happen before calling 286 | // setsid() and opening the pseudo-terminal so that the parent 287 | // is not affected 288 | int child = fork(); 289 | if (child < 0) { 290 | // fork failed, send a return code and bail out 291 | PLOGE("unable to fork"); 292 | write(fd, &child, sizeof(int)); 293 | close(fd); 294 | return child; 295 | } 296 | 297 | if (child != 0) { 298 | // In parent, wait for the child to exit, and send the exit code 299 | // across the wire. 300 | int status, code; 301 | 302 | free(pts_slave); 303 | 304 | LOGD("waiting for child exit"); 305 | if (waitpid(child, &status, 0) > 0) { 306 | code = WEXITSTATUS(status); 307 | } 308 | else { 309 | code = -1; 310 | } 311 | 312 | // Pass the return code back to the client 313 | LOGD("sending code"); 314 | if (write(fd, &code, sizeof(int)) != sizeof(int)) { 315 | PLOGE("unable to write exit code"); 316 | } 317 | 318 | close(fd); 319 | LOGD("child exited"); 320 | return code; 321 | } 322 | 323 | // We are in the child now 324 | // Close the unix socket file descriptor 325 | close (fd); 326 | 327 | // Become session leader 328 | if (setsid() == (pid_t) -1) { 329 | PLOGE("setsid"); 330 | } 331 | 332 | int ptsfd; 333 | if (pts_slave[0]) { 334 | // Opening the TTY has to occur after the 335 | // fork() and setsid() so that it becomes 336 | // our controlling TTY and not the daemon's 337 | ptsfd = open(pts_slave, O_RDWR); 338 | if (ptsfd == -1) { 339 | PLOGE("open(pts_slave) daemon"); 340 | exit(-1); 341 | } 342 | 343 | if (infd < 0) { 344 | LOGD("daemon: stdin using PTY"); 345 | infd = ptsfd; 346 | } 347 | if (outfd < 0) { 348 | LOGD("daemon: stdout using PTY"); 349 | outfd = ptsfd; 350 | } 351 | if (errfd < 0) { 352 | LOGD("daemon: stderr using PTY"); 353 | errfd = ptsfd; 354 | } 355 | } else { 356 | // If a TTY was sent directly, make it the CTTY. 357 | if (isatty(infd)) { 358 | ioctl(infd, TIOCSCTTY, 1); 359 | } 360 | } 361 | free(pts_slave); 362 | 363 | return run_daemon_child(infd, outfd, errfd, argc, argv); 364 | } 365 | 366 | int run_daemon() { 367 | if (getuid() != 0 || getgid() != 0) { 368 | PLOGE("daemon requires root. uid/gid not root"); 369 | return -1; 370 | } 371 | 372 | int fd; 373 | struct sockaddr_un sun; 374 | 375 | fd = socket(AF_LOCAL, SOCK_STREAM, 0); 376 | if (fd < 0) { 377 | PLOGE("socket"); 378 | return -1; 379 | } 380 | if (fcntl(fd, F_SETFD, FD_CLOEXEC)) { 381 | PLOGE("fcntl FD_CLOEXEC"); 382 | goto err; 383 | } 384 | 385 | memset(&sun, 0, sizeof(sun)); 386 | sun.sun_family = AF_LOCAL; 387 | sprintf(sun.sun_path, "%s/server", REQUESTOR_DAEMON_PATH); 388 | 389 | /* 390 | * Delete the socket to protect from situations when 391 | * something bad occured previously and the kernel reused pid from that process. 392 | * Small probability, isn't it. 393 | */ 394 | unlink(sun.sun_path); 395 | unlink(REQUESTOR_DAEMON_PATH); 396 | 397 | int previous_umask = umask(027); 398 | mkdir(REQUESTOR_DAEMON_PATH, 0777); 399 | 400 | memset(sun.sun_path, 0, sizeof(sun.sun_path)); 401 | memcpy(sun.sun_path, "\0" "SUPERUSER", strlen("SUPERUSER") + 1); 402 | 403 | if (bind(fd, (struct sockaddr*)&sun, sizeof(sun)) < 0) { 404 | PLOGE("daemon bind"); 405 | goto err; 406 | } 407 | 408 | chmod(REQUESTOR_DAEMON_PATH, 0755); 409 | chmod(sun.sun_path, 0777); 410 | 411 | umask(previous_umask); 412 | 413 | if (listen(fd, 10) < 0) { 414 | PLOGE("daemon listen"); 415 | goto err; 416 | } 417 | 418 | int client; 419 | while ((client = accept(fd, NULL, NULL)) > 0) { 420 | if (fork_zero_fucks() == 0) { 421 | close(fd); 422 | return daemon_accept(client); 423 | } 424 | else { 425 | close(client); 426 | } 427 | } 428 | 429 | LOGE("daemon exiting"); 430 | err: 431 | close(fd); 432 | return -1; 433 | } 434 | 435 | // List of signals which cause process termination 436 | static int quit_signals[] = { SIGALRM, SIGHUP, SIGPIPE, SIGQUIT, SIGTERM, SIGINT, 0 }; 437 | 438 | static void sighandler(int sig) { 439 | LOGE("Caught sig %d", sig); 440 | restore_stdin(); 441 | 442 | // Assume we'll only be called before death 443 | // See note before sigaction() in set_stdin_raw() 444 | // 445 | // Now, close all standard I/O to cause the pumps 446 | // to exit so we can continue and retrieve the exit 447 | // code 448 | close(STDIN_FILENO); 449 | close(STDOUT_FILENO); 450 | close(STDERR_FILENO); 451 | 452 | // Put back all the default handlers 453 | struct sigaction act; 454 | int i; 455 | 456 | memset(&act, '\0', sizeof(act)); 457 | act.sa_handler = SIG_DFL; 458 | for (i = 0; quit_signals[i]; i++) { 459 | if (sigaction(quit_signals[i], &act, NULL) < 0) { 460 | PLOGE("Error removing signal handler"); 461 | continue; 462 | } 463 | } 464 | } 465 | 466 | /** 467 | * Setup signal handlers trap signals which should result in program termination 468 | * so that we can restore the terminal to its normal state and retrieve the 469 | * return code. 470 | */ 471 | static void setup_sighandlers(void) { 472 | struct sigaction act; 473 | int i; 474 | 475 | // Install the termination handlers 476 | // Note: we're assuming that none of these signal handlers are already trapped. 477 | // If they are, we'll need to modify this code to save the previous handler and 478 | // call it after we restore stdin to its previous state. 479 | memset(&act, '\0', sizeof(act)); 480 | act.sa_handler = &sighandler; 481 | for (i = 0; quit_signals[i]; i++) { 482 | if (sigaction(quit_signals[i], &act, NULL) < 0) { 483 | PLOGE("Error installing signal handler"); 484 | continue; 485 | } 486 | } 487 | } 488 | 489 | int connect_daemon(int argc, char *argv[], int ppid) { 490 | int uid = getuid(); 491 | int ptmx; 492 | char pts_slave[PATH_MAX]; 493 | 494 | struct sockaddr_un sun; 495 | 496 | // Open a socket to the daemon 497 | int socketfd = socket(AF_LOCAL, SOCK_STREAM, 0); 498 | if (socketfd < 0) { 499 | PLOGE("socket"); 500 | exit(-1); 501 | } 502 | if (fcntl(socketfd, F_SETFD, FD_CLOEXEC)) { 503 | PLOGE("fcntl FD_CLOEXEC"); 504 | exit(-1); 505 | } 506 | 507 | memset(&sun, 0, sizeof(sun)); 508 | sun.sun_family = AF_LOCAL; 509 | sprintf(sun.sun_path, "%s/server", REQUESTOR_DAEMON_PATH); 510 | 511 | memset(sun.sun_path, 0, sizeof(sun.sun_path)); 512 | memcpy(sun.sun_path, "\0" "SUPERUSER", strlen("SUPERUSER") + 1); 513 | 514 | if (0 != connect(socketfd, (struct sockaddr*)&sun, sizeof(sun))) { 515 | PLOGE("connect"); 516 | exit(-1); 517 | } 518 | 519 | LOGD("connecting client %d", getpid()); 520 | 521 | // Determine which one of our streams are attached to a TTY 522 | int atty = 0; 523 | 524 | // Send TTYs directly (instead of proxying with a PTY) if 525 | // the SUPERUSER_SEND_TTY environment variable is set. 526 | if (getenv("SUPERUSER_SEND_TTY") == NULL) { 527 | if (isatty(STDIN_FILENO)) atty |= ATTY_IN; 528 | if (isatty(STDOUT_FILENO)) atty |= ATTY_OUT; 529 | if (isatty(STDERR_FILENO)) atty |= ATTY_ERR; 530 | } 531 | 532 | if (atty) { 533 | // We need a PTY. Get one. 534 | ptmx = pts_open(pts_slave, sizeof(pts_slave)); 535 | if (ptmx < 0) { 536 | PLOGE("pts_open"); 537 | exit(-1); 538 | } 539 | } else { 540 | pts_slave[0] = '\0'; 541 | } 542 | 543 | // Send some info to the daemon, starting with our PID 544 | write_int(socketfd, getpid()); 545 | // Send the slave path to the daemon 546 | // (This is "" if we're not using PTYs) 547 | write_string(socketfd, pts_slave); 548 | // User ID 549 | write_int(socketfd, uid); 550 | // Parent PID 551 | write_int(socketfd, ppid); 552 | 553 | // Send stdin 554 | if (atty & ATTY_IN) { 555 | // Using PTY 556 | send_fd(socketfd, -1); 557 | } else { 558 | send_fd(socketfd, STDIN_FILENO); 559 | } 560 | 561 | // Send stdout 562 | if (atty & ATTY_OUT) { 563 | // Forward SIGWINCH 564 | watch_sigwinch_async(STDOUT_FILENO, ptmx); 565 | 566 | // Using PTY 567 | send_fd(socketfd, -1); 568 | } else { 569 | send_fd(socketfd, STDOUT_FILENO); 570 | } 571 | 572 | // Send stderr 573 | if (atty & ATTY_ERR) { 574 | // Using PTY 575 | send_fd(socketfd, -1); 576 | } else { 577 | send_fd(socketfd, STDERR_FILENO); 578 | } 579 | 580 | // Number of command line arguments 581 | write_int(socketfd, argc); 582 | 583 | // Command line arguments 584 | int i; 585 | for (i = 0; i < argc; i++) { 586 | write_string(socketfd, argv[i]); 587 | } 588 | 589 | // Wait for acknowledgement from daemon 590 | read_int(socketfd); 591 | 592 | if (atty & ATTY_IN) { 593 | setup_sighandlers(); 594 | pump_stdin_async(ptmx); 595 | } 596 | if (atty & ATTY_OUT) { 597 | pump_stdout_blocking(ptmx); 598 | } 599 | 600 | // Get the exit code 601 | int code = read_int(socketfd); 602 | close(socketfd); 603 | LOGD("client exited %d", code); 604 | 605 | return code; 606 | } 607 | -------------------------------------------------------------------------------- /src/pts.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright 2021, Corellium, LLC 3 | * Copyright 2013, Tan Chee Eng (@tan-ce) 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /* 19 | * pts.c 20 | * 21 | * Manages the pseudo-terminal driver on Linux/Android and provides some 22 | * helper functions to handle raw input mode and terminal window resizing 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "pts.h" 35 | 36 | /** 37 | * Helper functions 38 | */ 39 | // Ensures all the data is written out 40 | static int write_blocking(int fd, char *buf, ssize_t bufsz) { 41 | ssize_t ret, written; 42 | 43 | written = 0; 44 | do { 45 | ret = write(fd, buf + written, bufsz - written); 46 | if (ret == -1) return -1; 47 | written += ret; 48 | } while (written < bufsz); 49 | 50 | return 0; 51 | } 52 | 53 | /** 54 | * Pump data from input FD to output FD. If close_output is 55 | * true, then close the output FD when we're done. 56 | */ 57 | static void pump_ex(int input, int output, int close_output) { 58 | char buf[4096]; 59 | int len; 60 | while ((len = read(input, buf, 4096)) > 0) { 61 | if (write_blocking(output, buf, len) == -1) break; 62 | } 63 | close(input); 64 | if (close_output) close(output); 65 | } 66 | 67 | /** 68 | * Pump data from input FD to output FD. Will close the 69 | * output FD when done. 70 | */ 71 | static void pump(int input, int output) { 72 | pump_ex(input, output, 1); 73 | } 74 | 75 | static void* pump_thread(void* data) { 76 | int* files = (int*)data; 77 | int input = files[0]; 78 | int output = files[1]; 79 | pump(input, output); 80 | free(data); 81 | return NULL; 82 | } 83 | 84 | static void pump_async(int input, int output) { 85 | pthread_t writer; 86 | int* files = (int*)malloc(sizeof(int) * 2); 87 | if (files == NULL) { 88 | exit(-1); 89 | } 90 | files[0] = input; 91 | files[1] = output; 92 | pthread_create(&writer, NULL, pump_thread, files); 93 | } 94 | 95 | 96 | /** 97 | * pts_open 98 | * 99 | * Opens a pts device and returns the name of the slave tty device. 100 | * 101 | * Arguments 102 | * slave_name the name of the slave device 103 | * slave_name_size the size of the buffer passed via slave_name 104 | * 105 | * Return Values 106 | * on failure either -2 or -1 (errno set) is returned. 107 | * on success, the file descriptor of the master device is returned. 108 | */ 109 | int pts_open(char *slave_name, size_t slave_name_size) { 110 | int fdm; 111 | char *sn_tmp; 112 | 113 | // Open master ptmx device 114 | fdm = open("/dev/ptmx", O_RDWR); 115 | if (fdm == -1) return -1; 116 | 117 | // Get the slave name 118 | sn_tmp = ptsname(fdm); 119 | if (!sn_tmp) { 120 | close(fdm); 121 | return -2; 122 | } 123 | 124 | strncpy(slave_name, sn_tmp, slave_name_size); 125 | slave_name[slave_name_size - 1] = '\0'; 126 | 127 | // Grant, then unlock 128 | if (grantpt(fdm) == -1) { 129 | close(fdm); 130 | return -1; 131 | } 132 | if (unlockpt(fdm) == -1) { 133 | close(fdm); 134 | return -1; 135 | } 136 | 137 | return fdm; 138 | } 139 | 140 | // Stores the previous termios of stdin 141 | static struct termios old_stdin; 142 | static int stdin_is_raw = 0; 143 | 144 | /** 145 | * set_stdin_raw 146 | * 147 | * Changes stdin to raw unbuffered mode, disables echo, 148 | * auto carriage return, etc. 149 | * 150 | * Return Value 151 | * on failure -1, and errno is set 152 | * on success 0 153 | */ 154 | int set_stdin_raw(void) { 155 | struct termios new_termios; 156 | 157 | // Save the current stdin termios 158 | if (tcgetattr(STDIN_FILENO, &old_stdin) < 0) { 159 | return -1; 160 | } 161 | 162 | // Start from the current settings 163 | new_termios = old_stdin; 164 | 165 | // Make the terminal like an SSH or telnet client 166 | new_termios.c_iflag |= IGNPAR; 167 | new_termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF); 168 | new_termios.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL); 169 | new_termios.c_oflag &= ~OPOST; 170 | new_termios.c_cc[VMIN] = 1; 171 | new_termios.c_cc[VTIME] = 0; 172 | 173 | if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_termios) < 0) { 174 | return -1; 175 | } 176 | 177 | stdin_is_raw = 1; 178 | 179 | return 0; 180 | } 181 | 182 | /** 183 | * restore_stdin 184 | * 185 | * Restore termios on stdin to the state it was before 186 | * set_stdin_raw() was called. If set_stdin_raw() was 187 | * never called, does nothing and doesn't return an error. 188 | * 189 | * This function is async-safe. 190 | * 191 | * Return Value 192 | * on failure, -1 and errno is set 193 | * on success, 0 194 | */ 195 | int restore_stdin(void) { 196 | if (!stdin_is_raw) return 0; 197 | 198 | if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_stdin) < 0) { 199 | return -1; 200 | } 201 | 202 | stdin_is_raw = 0; 203 | 204 | return 0; 205 | } 206 | 207 | // Flag indicating whether the sigwinch watcher should terminate. 208 | volatile static int closing_time = 0; 209 | 210 | /** 211 | * Thread process. Wait for a SIGWINCH to be received, then update 212 | * the terminal size. 213 | */ 214 | static void *watch_sigwinch(void *data) { 215 | sigset_t winch; 216 | int sig; 217 | int master = ((int *)data)[0]; 218 | int slave = ((int *)data)[1]; 219 | 220 | sigemptyset(&winch); 221 | sigaddset(&winch, SIGWINCH); 222 | 223 | do { 224 | // Wait for a SIGWINCH 225 | sigwait(&winch, &sig); 226 | 227 | if (closing_time) break; 228 | 229 | // Get the new terminal size 230 | struct winsize w; 231 | if (ioctl(master, TIOCGWINSZ, &w) == -1) { 232 | continue; 233 | } 234 | 235 | // Set the new terminal size 236 | ioctl(slave, TIOCSWINSZ, &w); 237 | 238 | } while (1); 239 | 240 | free(data); 241 | return NULL; 242 | } 243 | 244 | /** 245 | * watch_sigwinch_async 246 | * 247 | * After calling this function, if the application receives 248 | * SIGWINCH, the terminal window size will be read from 249 | * "input" and set on "output". 250 | * 251 | * NOTE: This function blocks SIGWINCH and spawns a thread. 252 | * NOTE 2: This function must be called before any of the 253 | * pump functions. 254 | * 255 | * Arguments 256 | * master A file descriptor of the TTY window size to follow 257 | * slave A file descriptor of the TTY window size which is 258 | * to be set on SIGWINCH 259 | * 260 | * Return Value 261 | * on failure, -1 and errno will be set. In this case, no 262 | * thread has been spawned and SIGWINCH will not be 263 | * blocked. 264 | * on success, 0 265 | */ 266 | int watch_sigwinch_async(int master, int slave) { 267 | pthread_t watcher; 268 | int *files = (int *) malloc(sizeof(int) * 2); 269 | if (files == NULL) { 270 | return -1; 271 | } 272 | 273 | // Block SIGWINCH so sigwait can later receive it 274 | sigset_t winch; 275 | sigemptyset(&winch); 276 | sigaddset(&winch, SIGWINCH); 277 | if (sigprocmask(SIG_BLOCK, &winch, NULL) == -1) { 278 | free(files); 279 | return -1; 280 | } 281 | 282 | // Initialize some variables, then start the thread 283 | closing_time = 0; 284 | files[0] = master; 285 | files[1] = slave; 286 | int ret = pthread_create(&watcher, NULL, &watch_sigwinch, files); 287 | if (ret != 0) { 288 | free(files); 289 | errno = ret; 290 | return -1; 291 | } 292 | 293 | // Set the initial terminal size 294 | raise(SIGWINCH); 295 | return 0; 296 | } 297 | 298 | /** 299 | * watch_sigwinch_cleanup 300 | * 301 | * Cause the SIGWINCH watcher thread to terminate 302 | */ 303 | void watch_sigwinch_cleanup(void) { 304 | closing_time = 1; 305 | raise(SIGWINCH); 306 | } 307 | 308 | /** 309 | * pump_stdin_async 310 | * 311 | * Forward data from STDIN to the given FD 312 | * in a seperate thread 313 | */ 314 | void pump_stdin_async(int outfd) { 315 | // Put stdin into raw mode 316 | set_stdin_raw(); 317 | 318 | // Pump data from stdin to the PTY 319 | pump_async(STDIN_FILENO, outfd); 320 | } 321 | 322 | /** 323 | * pump_stdout_blocking 324 | * 325 | * Forward data from the FD to STDOUT. 326 | * Returns when the remote end of the FD closes. 327 | * 328 | * Before returning, restores stdin settings. 329 | */ 330 | void pump_stdout_blocking(int infd) { 331 | // Pump data from stdout to PTY 332 | pump_ex(infd, STDOUT_FILENO, 0 /* Don't close output when done */); 333 | 334 | // Cleanup 335 | restore_stdin(); 336 | watch_sigwinch_cleanup(); 337 | } 338 | -------------------------------------------------------------------------------- /src/su.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright 2021, Corellium, LLC 3 | ** Copyright 2010, Adam Shanks (@ChainsDD) 4 | ** Copyright 2008, Zinx Verituse (@zinxv) 5 | ** 6 | ** Licensed under the Apache License, Version 2.0 (the "License"); 7 | ** you may not use this file except in compliance with the License. 8 | ** You may obtain a copy of the License at 9 | ** 10 | ** http://www.apache.org/licenses/LICENSE-2.0 11 | ** 12 | ** Unless required by applicable law or agreed to in writing, software 13 | ** distributed under the License is distributed on an "AS IS" BASIS, 14 | ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | ** See the License for the specific language governing permissions and 16 | ** limitations under the License. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #include "su.h" 39 | #include "utils.h" 40 | 41 | extern int is_daemon; 42 | extern int daemon_from_uid; 43 | extern int daemon_from_pid; 44 | 45 | unsigned get_shell_uid() { 46 | struct passwd* ppwd = getpwnam("shell"); 47 | if (NULL == ppwd) { 48 | return 2000; 49 | } 50 | 51 | return ppwd->pw_uid; 52 | } 53 | 54 | unsigned get_system_uid() { 55 | struct passwd* ppwd = getpwnam("system"); 56 | if (NULL == ppwd) { 57 | return 1000; 58 | } 59 | 60 | return ppwd->pw_uid; 61 | } 62 | 63 | unsigned get_radio_uid() { 64 | struct passwd* ppwd = getpwnam("radio"); 65 | if (NULL == ppwd) { 66 | return 1001; 67 | } 68 | 69 | return ppwd->pw_uid; 70 | } 71 | 72 | int fork_zero_fucks() { 73 | int pid = fork(); 74 | if (pid) { 75 | int status; 76 | waitpid(pid, &status, 0); 77 | return pid; 78 | } 79 | else { 80 | if ((pid = fork())) 81 | exit(0); 82 | return 0; 83 | } 84 | } 85 | 86 | static int from_init(struct su_initiator *from) { 87 | char path[PATH_MAX], exe[PATH_MAX]; 88 | char args[4096], *argv0, *argv_rest; 89 | int fd; 90 | ssize_t len; 91 | int i; 92 | int err; 93 | 94 | from->uid = getuid(); 95 | from->pid = getppid(); 96 | 97 | if (is_daemon) { 98 | from->uid = daemon_from_uid; 99 | from->pid = daemon_from_pid; 100 | } 101 | 102 | /* Get the command line */ 103 | snprintf(path, sizeof(path), "/proc/%u/cmdline", from->pid); 104 | fd = open(path, O_RDONLY); 105 | if (fd < 0) { 106 | PLOGE("Opening command line"); 107 | return -1; 108 | } 109 | len = read(fd, args, sizeof(args)); 110 | err = errno; 111 | close(fd); 112 | if (len < 0 || len == sizeof(args)) { 113 | PLOGEV("Reading command line", err); 114 | return -1; 115 | } 116 | 117 | argv0 = args; 118 | argv_rest = NULL; 119 | for (i = 0; i < len; i++) { 120 | if (args[i] == '\0') { 121 | if (!argv_rest) { 122 | argv_rest = &args[i+1]; 123 | } else { 124 | args[i] = ' '; 125 | } 126 | } 127 | } 128 | args[len] = '\0'; 129 | 130 | if (argv_rest) { 131 | strncpy(from->args, argv_rest, sizeof(from->args)); 132 | from->args[sizeof(from->args)-1] = '\0'; 133 | } else { 134 | from->args[0] = '\0'; 135 | } 136 | 137 | /* If this isn't app_process, use the real path instead of argv[0] */ 138 | snprintf(path, sizeof(path), "/proc/%u/exe", from->pid); 139 | len = readlink(path, exe, sizeof(exe)); 140 | if (len < 0) { 141 | PLOGE("Getting exe path"); 142 | return -1; 143 | } 144 | exe[len] = '\0'; 145 | if (strcmp(exe, "/system/bin/app_process")) { 146 | argv0 = exe; 147 | } 148 | 149 | strncpy(from->bin, argv0, sizeof(from->bin)); 150 | from->bin[sizeof(from->bin)-1] = '\0'; 151 | 152 | struct passwd *pw; 153 | pw = getpwuid(from->uid); 154 | if (pw && pw->pw_name) { 155 | strncpy(from->name, pw->pw_name, sizeof(from->name)); 156 | } 157 | 158 | return 0; 159 | } 160 | 161 | static void user_init(struct su_context *ctx) { 162 | if (ctx->from.uid > 99999) { 163 | ctx->user.android_user_id = ctx->from.uid / 100000; 164 | } 165 | } 166 | 167 | static void populate_environment(const struct su_context *ctx) { 168 | struct passwd *pw; 169 | 170 | if (ctx->to.keepenv) 171 | return; 172 | 173 | pw = getpwuid(ctx->to.uid); 174 | if (pw) { 175 | setenv("HOME", pw->pw_dir, 1); 176 | if (ctx->to.shell) 177 | setenv("SHELL", ctx->to.shell, 1); 178 | else 179 | setenv("SHELL", DEFAULT_SHELL, 1); 180 | if (ctx->to.login || ctx->to.uid) { 181 | setenv("USER", pw->pw_name, 1); 182 | setenv("LOGNAME", pw->pw_name, 1); 183 | } 184 | } 185 | } 186 | 187 | void set_identity(unsigned int uid) { 188 | /* 189 | * Set effective uid back to root, otherwise setres[ug]id will fail 190 | * if uid isn't root. 191 | */ 192 | if (seteuid(0)) { 193 | PLOGE("seteuid (root)"); 194 | exit(EXIT_FAILURE); 195 | } 196 | if (setresgid(uid, uid, uid)) { 197 | PLOGE("setresgid (%u)", uid); 198 | exit(EXIT_FAILURE); 199 | } 200 | if (setresuid(uid, uid, uid)) { 201 | PLOGE("setresuid (%u)", uid); 202 | exit(EXIT_FAILURE); 203 | } 204 | } 205 | 206 | static void socket_cleanup(struct su_context *ctx) { 207 | if (ctx && ctx->sock_path[0]) { 208 | if (unlink(ctx->sock_path)) 209 | PLOGE("unlink (%s)", ctx->sock_path); 210 | ctx->sock_path[0] = 0; 211 | } 212 | } 213 | 214 | /* 215 | * For use in signal handlers/atexit-function 216 | * NOTE: su_ctx points to main's local variable. 217 | * It's OK due to the program uses exit(3), not return from main() 218 | */ 219 | static struct su_context *su_ctx = NULL; 220 | 221 | static void cleanup(void) { 222 | socket_cleanup(su_ctx); 223 | } 224 | 225 | static void cleanup_signal(int sig) { 226 | socket_cleanup(su_ctx); 227 | exit(128 + sig); 228 | } 229 | 230 | static int socket_create_temp(char *path, size_t len) { 231 | int fd; 232 | struct sockaddr_un sun; 233 | 234 | fd = socket(AF_LOCAL, SOCK_STREAM, 0); 235 | if (fd < 0) { 236 | PLOGE("socket"); 237 | return -1; 238 | } 239 | if (fcntl(fd, F_SETFD, FD_CLOEXEC)) { 240 | PLOGE("fcntl FD_CLOEXEC"); 241 | goto err; 242 | } 243 | 244 | memset(&sun, 0, sizeof(sun)); 245 | sun.sun_family = AF_LOCAL; 246 | snprintf(path, len, "%s/.socket%d", REQUESTOR_CACHE_PATH, getpid()); 247 | memset(sun.sun_path, 0, sizeof(sun.sun_path)); 248 | snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", path); 249 | 250 | /* 251 | * Delete the socket to protect from situations when 252 | * something bad occured previously and the kernel reused pid from that process. 253 | * Small probability, isn't it. 254 | */ 255 | unlink(sun.sun_path); 256 | 257 | if (bind(fd, (struct sockaddr*)&sun, sizeof(sun)) < 0) { 258 | PLOGE("bind"); 259 | goto err; 260 | } 261 | 262 | if (listen(fd, 1) < 0) { 263 | PLOGE("listen"); 264 | goto err; 265 | } 266 | 267 | return fd; 268 | err: 269 | close(fd); 270 | return -1; 271 | } 272 | 273 | static int socket_accept(int serv_fd) { 274 | struct timeval tv; 275 | fd_set fds; 276 | int fd, rc; 277 | 278 | /* Wait 20 seconds for a connection, then give up. */ 279 | tv.tv_sec = 20; 280 | tv.tv_usec = 0; 281 | FD_ZERO(&fds); 282 | FD_SET(serv_fd, &fds); 283 | do { 284 | rc = select(serv_fd + 1, &fds, NULL, NULL, &tv); 285 | } while (rc < 0 && errno == EINTR); 286 | if (rc < 1) { 287 | PLOGE("select"); 288 | return -1; 289 | } 290 | 291 | fd = accept(serv_fd, NULL, NULL); 292 | if (fd < 0) { 293 | PLOGE("accept"); 294 | return -1; 295 | } 296 | 297 | return fd; 298 | } 299 | 300 | static int socket_send_request(int fd, const struct su_context *ctx) { 301 | #define write_data(fd, data, data_len) \ 302 | do { \ 303 | size_t __len = htonl(data_len); \ 304 | __len = write((fd), &__len, sizeof(__len)); \ 305 | if (__len != sizeof(__len)) { \ 306 | PLOGE("write(" #data ")"); \ 307 | return -1; \ 308 | } \ 309 | __len = write((fd), data, data_len); \ 310 | if (__len != data_len) { \ 311 | PLOGE("write(" #data ")"); \ 312 | return -1; \ 313 | } \ 314 | } while (0) 315 | 316 | #define write_string_data(fd, name, data) \ 317 | do { \ 318 | write_data(fd, name, strlen(name)); \ 319 | write_data(fd, data, strlen(data)); \ 320 | } while (0) 321 | 322 | // stringify everything. 323 | #define write_token(fd, name, data) \ 324 | do { \ 325 | char buf[16]; \ 326 | snprintf(buf, sizeof(buf), "%d", data); \ 327 | write_string_data(fd, name, buf); \ 328 | } while (0) 329 | 330 | write_token(fd, "version", PROTO_VERSION); 331 | write_token(fd, "binary.version", VERSION_CODE); 332 | write_token(fd, "pid", ctx->from.pid); 333 | write_string_data(fd, "from.name", ctx->from.name); 334 | write_string_data(fd, "to.name", ctx->to.name); 335 | write_token(fd, "from.uid", ctx->from.uid); 336 | write_token(fd, "to.uid", ctx->to.uid); 337 | write_string_data(fd, "from.bin", ctx->from.bin); 338 | // TODO: Fix issue where not using -c does not result a in a command 339 | write_string_data(fd, "command", get_command(&ctx->to)); 340 | write_token(fd, "eof", PROTO_VERSION); 341 | return 0; 342 | } 343 | 344 | static int socket_receive_result(int fd, char *result, ssize_t result_len) { 345 | ssize_t len; 346 | 347 | LOGD("waiting for user"); 348 | len = read(fd, result, result_len-1); 349 | if (len < 0) { 350 | PLOGE("read(result)"); 351 | return -1; 352 | } 353 | result[len] = '\0'; 354 | 355 | return 0; 356 | } 357 | 358 | static void usage(int status) { 359 | FILE *stream = (status == EXIT_SUCCESS) ? stdout : stderr; 360 | 361 | fprintf(stream, 362 | "Usage: su [options] [--] [-] [LOGIN] [--] [args...]\n\n" 363 | "Options:\n" 364 | " --daemon start the su daemon agent\n" 365 | " -c, --command COMMAND pass COMMAND to the invoked shell\n" 366 | " -h, --help display this help message and exit\n" 367 | " -, -l, --login pretend the shell to be a login shell\n" 368 | " -m, -p,\n" 369 | " --preserve-environment do not change environment variables\n" 370 | " -s, --shell SHELL use SHELL instead of the default " DEFAULT_SHELL "\n" 371 | " -v, --version display version number and exit\n" 372 | " -V display version code and exit,\n"); 373 | exit(status); 374 | } 375 | 376 | static __attribute__ ((noreturn)) void fail(struct su_context *ctx) { 377 | char *cmd = get_command(&ctx->to); 378 | 379 | LOGW("request failed (%u->%u %s)", ctx->from.uid, ctx->to.uid, cmd); 380 | fprintf(stderr, "%s\n", strerror(EACCES)); 381 | exit(EXIT_FAILURE); 382 | } 383 | 384 | static __attribute__ ((noreturn)) void allow(struct su_context *ctx) { 385 | char *arg0; 386 | int argc, err; 387 | 388 | umask(ctx->umask); 389 | 390 | char *binary; 391 | argc = ctx->to.optind; 392 | if (ctx->to.command) { 393 | binary = ctx->to.shell; 394 | ctx->to.argv[--argc] = ctx->to.command; 395 | ctx->to.argv[--argc] = "-c"; 396 | } 397 | else if (ctx->to.shell) { 398 | binary = ctx->to.shell; 399 | } 400 | else { 401 | if (ctx->to.argv[argc]) { 402 | binary = ctx->to.argv[argc++]; 403 | } 404 | else { 405 | binary = DEFAULT_SHELL; 406 | } 407 | } 408 | 409 | arg0 = strrchr (binary, '/'); 410 | arg0 = (arg0) ? arg0 + 1 : binary; 411 | if (ctx->to.login) { 412 | int s = strlen(arg0) + 2; 413 | char *p = malloc(s); 414 | 415 | if (!p) 416 | exit(EXIT_FAILURE); 417 | 418 | *p = '-'; 419 | strcpy(p + 1, arg0); 420 | arg0 = p; 421 | } 422 | 423 | populate_environment(ctx); 424 | set_identity(ctx->to.uid); 425 | 426 | #define PARG(arg) \ 427 | (argc + (arg) < ctx->to.argc) ? " " : "", \ 428 | (argc + (arg) < ctx->to.argc) ? ctx->to.argv[argc + (arg)] : "" 429 | 430 | LOGD("%u %s executing %u %s using binary %s : %s%s%s%s%s%s%s%s%s%s%s%s%s%s", 431 | ctx->from.uid, ctx->from.bin, 432 | ctx->to.uid, get_command(&ctx->to), binary, 433 | arg0, PARG(0), PARG(1), PARG(2), PARG(3), PARG(4), PARG(5), 434 | (ctx->to.optind + 6 < ctx->to.argc) ? " ..." : ""); 435 | 436 | ctx->to.argv[--argc] = arg0; 437 | execvp(binary, ctx->to.argv + argc); 438 | err = errno; 439 | PLOGE("exec"); 440 | fprintf(stderr, "Cannot execute %s: %s\n", binary, strerror(err)); 441 | exit(EXIT_FAILURE); 442 | } 443 | 444 | static void fork_for_samsung(void) 445 | { 446 | // Samsung CONFIG_SEC_RESTRICT_SETUID wants the parent process to have 447 | // EUID 0, or else our setresuid() calls will be denied. So make sure 448 | // all such syscalls are executed by a child process. 449 | int rv; 450 | 451 | switch (fork()) { 452 | case 0: 453 | return; 454 | case -1: 455 | PLOGE("fork"); 456 | exit(1); 457 | default: 458 | if (wait(&rv) < 0) { 459 | exit(1); 460 | } else { 461 | exit(WEXITSTATUS(rv)); 462 | } 463 | } 464 | } 465 | 466 | int main(int argc, char *argv[]) { 467 | return su_main(argc, argv, 1); 468 | } 469 | 470 | int su_main(int argc, char *argv[], int need_client) { 471 | // start up in daemon mode if prompted 472 | if (argc == 2 && strcmp(argv[1], "--daemon") == 0) { 473 | return run_daemon(); 474 | } 475 | 476 | int ppid = getppid(); 477 | fork_for_samsung(); 478 | 479 | // Sanitize all secure environment variables (from linker_environ.c in AOSP linker). 480 | /* The same list than GLibc at this point */ 481 | static const char* const unsec_vars[] = { 482 | "GCONV_PATH", 483 | "GETCONF_DIR", 484 | "HOSTALIASES", 485 | "LD_AUDIT", 486 | "LD_DEBUG", 487 | "LD_DEBUG_OUTPUT", 488 | "LD_DYNAMIC_WEAK", 489 | "LD_LIBRARY_PATH", 490 | "LD_ORIGIN_PATH", 491 | "LD_PRELOAD", 492 | "LD_PROFILE", 493 | "LD_SHOW_AUXV", 494 | "LD_USE_LOAD_BIAS", 495 | "LOCALDOMAIN", 496 | "LOCPATH", 497 | "MALLOC_TRACE", 498 | "MALLOC_CHECK_", 499 | "NIS_PATH", 500 | "NLSPATH", 501 | "RESOLV_HOST_CONF", 502 | "RES_OPTIONS", 503 | "TMPDIR", 504 | "TZDIR", 505 | "LD_AOUT_LIBRARY_PATH", 506 | "LD_AOUT_PRELOAD", 507 | // not listed in linker, used due to system() call 508 | "IFS", 509 | }; 510 | const char* const* cp = unsec_vars; 511 | const char* const* endp = cp + sizeof(unsec_vars)/sizeof(unsec_vars[0]); 512 | while (cp < endp) { 513 | unsetenv(*cp); 514 | cp++; 515 | } 516 | 517 | LOGD("su invoked."); 518 | 519 | struct su_context ctx = { 520 | .from = { 521 | .pid = -1, 522 | .uid = 0, 523 | .bin = "", 524 | .args = "", 525 | .name = "", 526 | }, 527 | .to = { 528 | .uid = AID_ROOT, 529 | .login = 0, 530 | .keepenv = 0, 531 | .shell = NULL, 532 | .command = NULL, 533 | .argv = argv, 534 | .argc = argc, 535 | .optind = 0, 536 | .name = "", 537 | }, 538 | .user = { 539 | .android_user_id = 0, 540 | }, 541 | }; 542 | struct stat st; 543 | int c, socket_serv_fd, fd; 544 | char buf[64], *result; 545 | struct option long_opts[] = { 546 | { "command", required_argument, NULL, 'c' }, 547 | { "help", no_argument, NULL, 'h' }, 548 | { "login", no_argument, NULL, 'l' }, 549 | { "preserve-environment", no_argument, NULL, 'p' }, 550 | { "shell", required_argument, NULL, 's' }, 551 | { "version", no_argument, NULL, 'v' }, 552 | { NULL, 0, NULL, 0 }, 553 | }; 554 | 555 | while ((c = getopt_long(argc, argv, "+c:hlmps:Vv", long_opts, NULL)) != -1) { 556 | switch(c) { 557 | case 'c': 558 | ctx.to.shell = DEFAULT_SHELL; 559 | ctx.to.command = optarg; 560 | break; 561 | case 'h': 562 | usage(EXIT_SUCCESS); 563 | break; 564 | case 'l': 565 | ctx.to.login = 1; 566 | break; 567 | case 'm': 568 | case 'p': 569 | ctx.to.keepenv = 1; 570 | break; 571 | case 's': 572 | ctx.to.shell = optarg; 573 | break; 574 | case 'V': 575 | printf("%d\n", VERSION_CODE); 576 | exit(EXIT_SUCCESS); 577 | case 'v': 578 | printf("%s\n", VERSION); 579 | exit(EXIT_SUCCESS); 580 | default: 581 | /* Bionic getopt_long doesn't terminate its error output by newline */ 582 | fprintf(stderr, "\n"); 583 | usage(2); 584 | } 585 | } 586 | 587 | if (need_client) { 588 | LOGD("starting daemon client %d %d", getuid(), geteuid()); 589 | return connect_daemon(argc, argv, ppid); 590 | } 591 | 592 | if (optind < argc && !strcmp(argv[optind], "-")) { 593 | ctx.to.login = 1; 594 | optind++; 595 | } 596 | /* username or uid */ 597 | if (optind < argc && strcmp(argv[optind], "--")) { 598 | struct passwd *pw; 599 | pw = getpwnam(argv[optind]); 600 | if (!pw) { 601 | char *endptr; 602 | 603 | /* It seems we shouldn't do this at all */ 604 | errno = 0; 605 | ctx.to.uid = strtoul(argv[optind], &endptr, 10); 606 | if (errno || *endptr) { 607 | LOGE("Unknown id: %s\n", argv[optind]); 608 | fprintf(stderr, "Unknown id: %s\n", argv[optind]); 609 | exit(EXIT_FAILURE); 610 | } 611 | } else { 612 | ctx.to.uid = pw->pw_uid; 613 | if (pw->pw_name) 614 | strncpy(ctx.to.name, pw->pw_name, sizeof(ctx.to.name)); 615 | } 616 | optind++; 617 | } 618 | if (optind < argc && !strcmp(argv[optind], "--")) { 619 | optind++; 620 | } 621 | ctx.to.optind = optind; 622 | 623 | su_ctx = &ctx; 624 | if (from_init(&ctx.from) < 0) { 625 | fail(&ctx); 626 | } 627 | 628 | user_init(&ctx); 629 | 630 | // Allow everything 631 | allow(&ctx); 632 | 633 | // odd perms on superuser data dir 634 | if (st.st_gid != st.st_uid) { 635 | LOGE("Bad uid/gid %d/%d for Superuser Requestor application", 636 | (int)st.st_uid, (int)st.st_gid); 637 | fail(&ctx); 638 | } 639 | 640 | ctx.umask = umask(027); 641 | 642 | mkdir(REQUESTOR_CACHE_PATH, 0770); 643 | if (chown(REQUESTOR_CACHE_PATH, st.st_uid, st.st_gid)) { 644 | PLOGE("chown (%s, %u, %u)", REQUESTOR_CACHE_PATH, st.st_uid, st.st_gid); 645 | fail(&ctx); 646 | } 647 | 648 | if (setgroups(0, NULL)) { 649 | PLOGE("setgroups"); 650 | fail(&ctx); 651 | } 652 | if (setegid(st.st_gid)) { 653 | PLOGE("setegid (%u)", st.st_gid); 654 | fail(&ctx); 655 | } 656 | if (seteuid(st.st_uid)) { 657 | PLOGE("seteuid (%u)", st.st_uid); 658 | fail(&ctx); 659 | } 660 | 661 | socket_serv_fd = socket_create_temp(ctx.sock_path, sizeof(ctx.sock_path)); 662 | LOGD("%s", ctx.sock_path); 663 | if (socket_serv_fd < 0) { 664 | fail(&ctx); 665 | } 666 | 667 | signal(SIGHUP, cleanup_signal); 668 | signal(SIGPIPE, cleanup_signal); 669 | signal(SIGTERM, cleanup_signal); 670 | signal(SIGQUIT, cleanup_signal); 671 | signal(SIGINT, cleanup_signal); 672 | signal(SIGABRT, cleanup_signal); 673 | 674 | atexit(cleanup); 675 | 676 | fd = socket_accept(socket_serv_fd); 677 | if (fd < 0) { 678 | fail(&ctx); 679 | } 680 | if (socket_send_request(fd, &ctx)) { 681 | fail(&ctx); 682 | } 683 | if (socket_receive_result(fd, buf, sizeof(buf))) { 684 | fail(&ctx); 685 | } 686 | 687 | close(fd); 688 | close(socket_serv_fd); 689 | socket_cleanup(&ctx); 690 | 691 | result = buf; 692 | 693 | #define SOCKET_RESPONSE "socket:" 694 | if (strncmp(result, SOCKET_RESPONSE, sizeof(SOCKET_RESPONSE) - 1)) 695 | LOGW("SECURITY RISK: Requestor still receives credentials in intent"); 696 | else 697 | result += sizeof(SOCKET_RESPONSE) - 1; 698 | 699 | if (!strcmp(result, "fail")) { 700 | fail(&ctx); 701 | } else if (!strcmp(result, "ALLOW")) { 702 | allow(&ctx); 703 | } else { 704 | LOGE("unknown response from Superuser Requestor: %s", result); 705 | fail(&ctx); 706 | } 707 | } 708 | --------------------------------------------------------------------------------