├── .clang-format ├── .gitignore ├── Android.mk ├── LICENSE ├── binder ├── appops-wrapper.cpp ├── pm-wrapper.c └── pm-wrapper.h ├── daemon.c ├── pts.c ├── pts.h ├── su.c ├── su.h ├── superuser.rc ├── utils.c └── utils.h /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | AccessModifierOffset: -2 3 | AllowShortFunctionsOnASingleLine: Inline 4 | ColumnLimit: 100 5 | CommentPragmas: NOLINT:.* 6 | DerivePointerAlignment: false 7 | IndentWidth: 4 8 | PointerAlignment: Left 9 | TabWidth: 4 10 | UseTab: Never 11 | PenaltyExcessCharacter: 32 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | armeabi 3 | x86 4 | obj 5 | local.properties 6 | gen 7 | .DS_Store 8 | .settings 9 | libs 10 | -------------------------------------------------------------------------------- /Android.mk: -------------------------------------------------------------------------------- 1 | # Root AOSP source makefile 2 | # su is built here, and 3 | LOCAL_PATH := $(call my-dir) 4 | 5 | include $(CLEAR_VARS) 6 | 7 | LOCAL_MODULE := su 8 | LOCAL_MODULE_TAGS := optional 9 | LOCAL_SHARED_LIBRARIES := \ 10 | libbinder \ 11 | libcutils \ 12 | liblog \ 13 | libutils \ 14 | 15 | LOCAL_SRC_FILES := su.c daemon.c utils.c pts.c 16 | LOCAL_SRC_FILES += binder/appops-wrapper.cpp binder/pm-wrapper.c 17 | LOCAL_CFLAGS += -Werror -Wall 18 | LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) 19 | 20 | LOCAL_INIT_RC := superuser.rc 21 | 22 | include $(BUILD_EXECUTABLE) 23 | 24 | SYMLINKS := $(addprefix $(TARGET_OUT)/bin/,su) 25 | $(SYMLINKS): 26 | @echo "Symlink: $@ -> /system/xbin/su" 27 | @mkdir -p $(dir $@) 28 | @rm -rf $@ 29 | $(hide) ln -sf ../xbin/su $@ 30 | 31 | # We need this so that the installed files could be picked up based on the 32 | # local module name 33 | ALL_MODULES.$(LOCAL_MODULE).INSTALLED := \ 34 | $(ALL_MODULES.$(LOCAL_MODULE).INSTALLED) $(SYMLINKS) 35 | 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013 Koushik Dutta (2013) 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /binder/appops-wrapper.cpp: -------------------------------------------------------------------------------- 1 | #define LOG_TAG "su" 2 | 3 | #include 4 | #include 5 | 6 | using namespace android; 7 | 8 | extern "C" { 9 | 10 | int appops_start_op_su(int uid, const char* pkgName) { 11 | ALOGD("Checking whether app [uid:%d, pkgName: %s] is allowed to be root", uid, pkgName); 12 | 13 | AppOpsManager ops; 14 | int mode = ops.startOpNoThrow(AppOpsManager::OP_SU, uid, String16(pkgName), false); 15 | if (mode == AppOpsManager::MODE_ALLOWED) { 16 | ALOGD("Privilege elevation allowed by appops"); 17 | return 0; 18 | } 19 | 20 | ALOGD("Privilege elevation denied by appops"); 21 | return 1; 22 | } 23 | 24 | void appops_finish_op_su(int uid, const char* pkgName) { 25 | ALOGD("Finishing su operation for app [uid:%d, pkgName: %s]", uid, pkgName); 26 | AppOpsManager* ops = new AppOpsManager(); 27 | ops->finishOp(AppOpsManager::OP_SU, uid, String16(pkgName)); 28 | delete ops; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /binder/pm-wrapper.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "../utils.h" 7 | 8 | #define PACKAGE_LIST_PATH "/data/system/packages.list" 9 | #define PACKAGE_NAME_MAX_LEN (1 << 16) 10 | 11 | /* Tries to resolve a package name from a uid via the packages list file. 12 | * 13 | * If there is no matching uid, it will return an empty string which can 14 | * be resolved by appops in some cases (i.e. apps with uid = 0, uid = AID_SHELL). 15 | * 16 | * Since packages may share UID, this function will return the first present 17 | * in packages.list. 18 | */ 19 | char* resolve_package_name(int uid) { 20 | char* package_name = NULL; 21 | char* packages = read_file(PACKAGE_LIST_PATH); 22 | 23 | if (packages == NULL) { 24 | return NULL; 25 | } 26 | 27 | char* p = packages; 28 | while (*p) { 29 | char* line_end = strstr(p, "\n"); 30 | if (line_end == NULL) break; 31 | 32 | char* token; 33 | char* pkgName = strtok_r(p, " ", &token); 34 | if (pkgName != NULL) { 35 | char* pkgUid = strtok_r(NULL, " ", &token); 36 | if (pkgUid != NULL) { 37 | char* endptr; 38 | errno = 0; 39 | int pkgUidInt = strtoul(pkgUid, &endptr, 10); 40 | if ((errno == 0 && endptr != NULL && !(*endptr)) && pkgUidInt == uid) { 41 | package_name = strdup(pkgName); 42 | break; 43 | } 44 | } 45 | } 46 | p = ++line_end; 47 | } 48 | 49 | free(packages); 50 | return package_name; 51 | } 52 | -------------------------------------------------------------------------------- /binder/pm-wrapper.h: -------------------------------------------------------------------------------- 1 | #ifndef _HAS_PM_WRAPPER_H 2 | #define _HAS_PM_WRAPPER_H 3 | 4 | char* resolve_package_name(int uid); 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /daemon.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright 2010, Adam Shanks (@ChainsDD) 3 | ** Copyright 2008, Zinx Verituse (@zinxv) 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 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | #include "pts.h" 29 | #include "su.h" 30 | #include "utils.h" 31 | 32 | int is_daemon = 0; 33 | int daemon_from_uid = 0; 34 | int daemon_from_pid = 0; 35 | 36 | // Constants for the atty bitfield 37 | #define ATTY_IN 1 38 | #define ATTY_OUT 2 39 | #define ATTY_ERR 4 40 | 41 | /* 42 | * Receive a file descriptor from a Unix socket. 43 | * Contributed by @mkasick 44 | * 45 | * Returns the file descriptor on success, or -1 if a file 46 | * descriptor was not actually included in the message 47 | * 48 | * On error the function terminates by calling exit(-1) 49 | */ 50 | static int recv_fd(int sockfd) { 51 | // Need to receive data from the message, otherwise don't care about it. 52 | char iovbuf; 53 | 54 | struct iovec iov = { 55 | .iov_base = &iovbuf, 56 | .iov_len = 1, 57 | }; 58 | 59 | char cmsgbuf[CMSG_SPACE(sizeof(int))]; 60 | 61 | struct msghdr msg = { 62 | .msg_iov = &iov, 63 | .msg_iovlen = 1, 64 | .msg_control = cmsgbuf, 65 | .msg_controllen = sizeof(cmsgbuf), 66 | }; 67 | 68 | if (recvmsg(sockfd, &msg, MSG_WAITALL) != 1) { 69 | goto error; 70 | } 71 | 72 | // Was a control message actually sent? 73 | switch (msg.msg_controllen) { 74 | case 0: 75 | // No, so the file descriptor was closed and won't be used. 76 | return -1; 77 | case sizeof(cmsgbuf): 78 | // Yes, grab the file descriptor from it. 79 | break; 80 | default: 81 | goto error; 82 | } 83 | 84 | struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); 85 | 86 | if (cmsg == NULL || 87 | cmsg->cmsg_len != CMSG_LEN(sizeof(int)) || 88 | cmsg->cmsg_level != SOL_SOCKET || 89 | cmsg->cmsg_type != SCM_RIGHTS) { 90 | goto error; 91 | } 92 | 93 | return *(int*)CMSG_DATA(cmsg); 94 | 95 | error: 96 | ALOGE("unable to read fd"); 97 | exit(-1); 98 | } 99 | 100 | /* 101 | * Send a file descriptor through a Unix socket. 102 | * Contributed by @mkasick 103 | * 104 | * On error the function terminates by calling exit(-1) 105 | * 106 | * fd may be -1, in which case the dummy data is sent, 107 | * but no control message with the FD is sent. 108 | */ 109 | static void send_fd(int sockfd, int fd) { 110 | // Need to send some data in the message, this will do. 111 | struct iovec iov = { 112 | .iov_base = "", 113 | .iov_len = 1, 114 | }; 115 | 116 | struct msghdr msg = { 117 | .msg_iov = &iov, 118 | .msg_iovlen = 1, 119 | }; 120 | 121 | char cmsgbuf[CMSG_SPACE(sizeof(int))]; 122 | 123 | if (fd != -1) { 124 | // Is the file descriptor actually open? 125 | if (fcntl(fd, F_GETFD) == -1) { 126 | if (errno != EBADF) { 127 | goto error; 128 | } 129 | // It's closed, don't send a control message or sendmsg will EBADF. 130 | } else { 131 | // It's open, send the file descriptor in a control message. 132 | msg.msg_control = cmsgbuf; 133 | msg.msg_controllen = sizeof(cmsgbuf); 134 | 135 | struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); 136 | if (!cmsg) { 137 | goto error; 138 | } 139 | 140 | cmsg->cmsg_len = CMSG_LEN(sizeof(int)); 141 | cmsg->cmsg_level = SOL_SOCKET; 142 | cmsg->cmsg_type = SCM_RIGHTS; 143 | 144 | *(int*)CMSG_DATA(cmsg) = fd; 145 | } 146 | } 147 | 148 | if (sendmsg(sockfd, &msg, 0) != 1) { 149 | goto error; 150 | } 151 | 152 | return; 153 | 154 | error: 155 | PLOGE("unable to send fd"); 156 | exit(-1); 157 | } 158 | 159 | static int read_int(int fd) { 160 | int val; 161 | int len = read(fd, &val, sizeof(int)); 162 | if (len != sizeof(int)) { 163 | ALOGE("unable to read int: %d", len); 164 | exit(-1); 165 | } 166 | return val; 167 | } 168 | 169 | static void write_int(int fd, int val) { 170 | int written = write(fd, &val, sizeof(int)); 171 | if (written != sizeof(int)) { 172 | PLOGE("unable to write int"); 173 | exit(-1); 174 | } 175 | } 176 | 177 | static char* read_string(int fd) { 178 | int len = read_int(fd); 179 | if (len > PATH_MAX || len < 0) { 180 | ALOGE("invalid string length %d", len); 181 | exit(-1); 182 | } 183 | char* val = malloc(sizeof(char) * (len + 1)); 184 | if (val == NULL) { 185 | ALOGE("unable to malloc string"); 186 | exit(-1); 187 | } 188 | val[len] = '\0'; 189 | int amount = read(fd, val, len); 190 | if (amount != len) { 191 | ALOGE("unable to read string"); 192 | exit(-1); 193 | } 194 | return val; 195 | } 196 | 197 | static void write_string(int fd, char* val) { 198 | int len = strlen(val); 199 | write_int(fd, len); 200 | int written = write(fd, val, len); 201 | if (written != len) { 202 | PLOGE("unable to write string"); 203 | exit(-1); 204 | } 205 | } 206 | 207 | static int run_daemon_child(int infd, int outfd, int errfd, int argc, char** argv) { 208 | if (-1 == dup2(outfd, STDOUT_FILENO)) { 209 | PLOGE("dup2 child outfd"); 210 | exit(-1); 211 | } 212 | 213 | if (-1 == dup2(errfd, STDERR_FILENO)) { 214 | PLOGE("dup2 child errfd"); 215 | exit(-1); 216 | } 217 | 218 | if (-1 == dup2(infd, STDIN_FILENO)) { 219 | PLOGE("dup2 child infd"); 220 | exit(-1); 221 | } 222 | 223 | close(infd); 224 | close(outfd); 225 | close(errfd); 226 | 227 | return su_main(argc, argv, 0); 228 | } 229 | 230 | static int daemon_accept(int fd) { 231 | is_daemon = 1; 232 | int pid = read_int(fd); 233 | int child_result; 234 | ALOGD("remote pid: %d", pid); 235 | char* pts_slave = read_string(fd); 236 | ALOGD("remote pts_slave: %s", pts_slave); 237 | daemon_from_pid = read_int(fd); 238 | ALOGV("remote req pid: %d", daemon_from_pid); 239 | 240 | struct ucred credentials; 241 | socklen_t ucred_length = sizeof(struct ucred); 242 | /* fill in the user data structure */ 243 | if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &credentials, &ucred_length)) { 244 | ALOGE("could obtain credentials from unix domain socket"); 245 | exit(-1); 246 | } 247 | 248 | daemon_from_uid = credentials.uid; 249 | 250 | // The the FDs for each of the streams 251 | int infd = recv_fd(fd); 252 | int outfd = recv_fd(fd); 253 | int errfd = recv_fd(fd); 254 | 255 | int argc = read_int(fd); 256 | if (argc < 0 || argc > 512) { 257 | ALOGE("unable to allocate args: %d", argc); 258 | exit(-1); 259 | } 260 | ALOGV("remote args: %d", argc); 261 | char** argv = (char**)malloc(sizeof(char*) * (argc + 1)); 262 | if (!argv) { 263 | ALOGE("unable to allocate memory\n"); 264 | exit(-1); 265 | } 266 | argv[argc] = NULL; 267 | int i; 268 | for (i = 0; i < argc; i++) { 269 | argv[i] = read_string(fd); 270 | } 271 | 272 | // ack 273 | write_int(fd, 1); 274 | 275 | // Fork the child process. The fork has to happen before calling 276 | // setsid() and opening the pseudo-terminal so that the parent 277 | // is not affected 278 | int child = fork(); 279 | if (child < 0) { 280 | for (i = 0; i < argc; i++) { 281 | free(argv[i]); 282 | } 283 | free(argv); 284 | 285 | // fork failed, send a return code and bail out 286 | PLOGE("unable to fork"); 287 | write(fd, &child, sizeof(int)); 288 | close(fd); 289 | return child; 290 | } 291 | 292 | if (child != 0) { 293 | for (i = 0; i < argc; i++) { 294 | free(argv[i]); 295 | } 296 | free(argv); 297 | 298 | // In parent, wait for the child to exit, and send the exit code 299 | // across the wire. 300 | int code, status; 301 | 302 | free(pts_slave); 303 | 304 | ALOGD("waiting for child exit"); 305 | if (waitpid(child, &status, 0) > 0) { 306 | code = WEXITSTATUS(status); 307 | } else { 308 | code = -1; 309 | } 310 | 311 | // Is the file descriptor actually open? 312 | if (fcntl(fd, F_GETFD) == -1) { 313 | if (errno != EBADF) { 314 | return code; 315 | } 316 | } 317 | 318 | // Pass the return code back to the client 319 | ALOGD("sending code"); 320 | if (send(fd, &code, sizeof(int), MSG_NOSIGNAL) != sizeof(int)) { 321 | PLOGE("unable to write exit code"); 322 | } 323 | 324 | close(fd); 325 | ALOGD("child exited"); 326 | return code; 327 | } 328 | 329 | // We are in the child now 330 | // Close the unix socket file descriptor 331 | close(fd); 332 | 333 | // Become session leader 334 | if (setsid() == (pid_t)-1) { 335 | PLOGE("setsid"); 336 | } 337 | 338 | int ptsfd; 339 | if (pts_slave[0]) { 340 | // Opening the TTY has to occur after the 341 | // fork() and setsid() so that it becomes 342 | // our controlling TTY and not the daemon's 343 | ptsfd = open(pts_slave, O_RDWR); 344 | if (ptsfd == -1) { 345 | PLOGE("open(pts_slave) daemon"); 346 | exit(-1); 347 | } 348 | 349 | struct stat st; 350 | if (fstat(ptsfd, &st)) { 351 | PLOGE("failed to stat pts_slave"); 352 | exit(-1); 353 | } 354 | 355 | if (st.st_uid != credentials.uid) { 356 | PLOGE("caller doesn't own proposed PTY"); 357 | exit(-1); 358 | } 359 | 360 | if (!S_ISCHR(st.st_mode)) { 361 | PLOGE("proposed PTY isn't a chardev"); 362 | exit(-1); 363 | } 364 | 365 | if (infd < 0) { 366 | ALOGD("daemon: stdin using PTY"); 367 | infd = ptsfd; 368 | } 369 | if (outfd < 0) { 370 | ALOGD("daemon: stdout using PTY"); 371 | outfd = ptsfd; 372 | } 373 | if (errfd < 0) { 374 | ALOGD("daemon: stderr using PTY"); 375 | errfd = ptsfd; 376 | } 377 | } else { 378 | // TODO: Check system property, if PTYs are disabled, 379 | // made infd the CTTY using: 380 | // ioctl(infd, TIOCSCTTY, 1); 381 | } 382 | free(pts_slave); 383 | 384 | child_result = run_daemon_child(infd, outfd, errfd, argc, argv); 385 | for (i = 0; i < argc; i++) { 386 | free(argv[i]); 387 | } 388 | free(argv); 389 | return child_result; 390 | } 391 | 392 | int run_daemon() { 393 | if (getuid() != 0 || getgid() != 0) { 394 | PLOGE("daemon requires root. uid/gid not root"); 395 | return -1; 396 | } 397 | 398 | int fd; 399 | struct sockaddr_un sun; 400 | 401 | fd = socket(AF_LOCAL, SOCK_STREAM, 0); 402 | if (fd < 0) { 403 | PLOGE("socket"); 404 | return -1; 405 | } 406 | if (fcntl(fd, F_SETFD, FD_CLOEXEC)) { 407 | PLOGE("fcntl FD_CLOEXEC"); 408 | goto err; 409 | } 410 | 411 | memset(&sun, 0, sizeof(sun)); 412 | sun.sun_family = AF_LOCAL; 413 | sprintf(sun.sun_path, "%s/su-daemon", DAEMON_SOCKET_PATH); 414 | 415 | /* 416 | * Delete the socket to protect from situations when 417 | * something bad occured previously and the kernel reused pid from that process. 418 | * Small probability, isn't it. 419 | */ 420 | unlink(sun.sun_path); 421 | unlink(DAEMON_SOCKET_PATH); 422 | 423 | int previous_umask = umask(027); 424 | mkdir(DAEMON_SOCKET_PATH, 0711); 425 | 426 | if (bind(fd, (struct sockaddr*)&sun, sizeof(sun)) < 0) { 427 | PLOGE("daemon bind"); 428 | goto err; 429 | } 430 | 431 | chmod(DAEMON_SOCKET_PATH, 0711); 432 | chmod(sun.sun_path, 0666); 433 | 434 | umask(previous_umask); 435 | 436 | if (listen(fd, 10) < 0) { 437 | PLOGE("daemon listen"); 438 | goto err; 439 | } 440 | 441 | int client; 442 | while ((client = accept(fd, NULL, NULL)) > 0) { 443 | if (fork_zero_fucks() == 0) { 444 | close(fd); 445 | return daemon_accept(client); 446 | } else { 447 | close(client); 448 | } 449 | } 450 | 451 | ALOGE("daemon exiting"); 452 | err: 453 | close(fd); 454 | return -1; 455 | } 456 | 457 | // List of signals which cause process termination 458 | static int quit_signals[] = {SIGALRM, SIGHUP, SIGPIPE, SIGQUIT, SIGTERM, SIGINT, 0}; 459 | 460 | static void sighandler(__attribute__((unused)) int sig) { 461 | restore_stdin(); 462 | 463 | // Assume we'll only be called before death 464 | // See note before sigaction() in set_stdin_raw() 465 | // 466 | // Now, close all standard I/O to cause the pumps 467 | // to exit so we can continue and retrieve the exit 468 | // code 469 | close(STDIN_FILENO); 470 | close(STDOUT_FILENO); 471 | close(STDERR_FILENO); 472 | 473 | // Put back all the default handlers 474 | struct sigaction act; 475 | int i; 476 | 477 | memset(&act, '\0', sizeof(act)); 478 | act.sa_handler = SIG_DFL; 479 | for (i = 0; quit_signals[i]; i++) { 480 | if (sigaction(quit_signals[i], &act, NULL) < 0) { 481 | PLOGE("Error removing signal handler"); 482 | continue; 483 | } 484 | } 485 | } 486 | 487 | /** 488 | * Setup signal handlers trap signals which should result in program termination 489 | * so that we can restore the terminal to its normal state and retrieve the 490 | * return code. 491 | */ 492 | static void setup_sighandlers(void) { 493 | struct sigaction act; 494 | int i; 495 | 496 | // Install the termination handlers 497 | // Note: we're assuming that none of these signal handlers are already trapped. 498 | // If they are, we'll need to modify this code to save the previous handler and 499 | // call it after we restore stdin to its previous state. 500 | memset(&act, '\0', sizeof(act)); 501 | act.sa_handler = &sighandler; 502 | for (i = 0; quit_signals[i]; i++) { 503 | if (sigaction(quit_signals[i], &act, NULL) < 0) { 504 | PLOGE("Error installing signal handler"); 505 | continue; 506 | } 507 | } 508 | } 509 | 510 | int connect_daemon(int argc, char* argv[], int ppid) { 511 | int ptmx = -1; 512 | char pts_slave[PATH_MAX]; 513 | 514 | struct sockaddr_un sun; 515 | 516 | // Open a socket to the daemon 517 | int socketfd = socket(AF_LOCAL, SOCK_STREAM, 0); 518 | if (socketfd < 0) { 519 | PLOGE("socket"); 520 | exit(-1); 521 | } 522 | if (fcntl(socketfd, F_SETFD, FD_CLOEXEC)) { 523 | PLOGE("fcntl FD_CLOEXEC"); 524 | exit(-1); 525 | } 526 | 527 | memset(&sun, 0, sizeof(sun)); 528 | sun.sun_family = AF_LOCAL; 529 | sprintf(sun.sun_path, "%s/su-daemon", DAEMON_SOCKET_PATH); 530 | 531 | if (0 != connect(socketfd, (struct sockaddr*)&sun, sizeof(sun))) { 532 | PLOGE("connect"); 533 | exit(-1); 534 | } 535 | 536 | ALOGV("connecting client %d", getpid()); 537 | 538 | // Determine which one of our streams are attached to a TTY 539 | int atty = 0; 540 | 541 | // TODO: Check a system property and never use PTYs if 542 | // the property is set. 543 | if (isatty(STDIN_FILENO)) atty |= ATTY_IN; 544 | if (isatty(STDOUT_FILENO)) atty |= ATTY_OUT; 545 | if (isatty(STDERR_FILENO)) atty |= ATTY_ERR; 546 | 547 | if (atty) { 548 | // We need a PTY. Get one. 549 | ptmx = pts_open(pts_slave, sizeof(pts_slave)); 550 | if (ptmx < 0) { 551 | PLOGE("pts_open"); 552 | exit(-1); 553 | } 554 | } else { 555 | pts_slave[0] = '\0'; 556 | } 557 | 558 | // Send some info to the daemon, starting with our PID 559 | write_int(socketfd, getpid()); 560 | // Send the slave path to the daemon 561 | // (This is "" if we're not using PTYs) 562 | write_string(socketfd, pts_slave); 563 | // Parent PID 564 | write_int(socketfd, ppid); 565 | 566 | // Send stdin 567 | if (atty & ATTY_IN) { 568 | // Using PTY 569 | send_fd(socketfd, -1); 570 | } else { 571 | send_fd(socketfd, STDIN_FILENO); 572 | } 573 | 574 | // Send stdout 575 | if (atty & ATTY_OUT) { 576 | // Forward SIGWINCH 577 | watch_sigwinch_async(STDOUT_FILENO, ptmx); 578 | 579 | // Using PTY 580 | send_fd(socketfd, -1); 581 | } else { 582 | send_fd(socketfd, STDOUT_FILENO); 583 | } 584 | 585 | // Send stderr 586 | if (atty & ATTY_ERR) { 587 | // Using PTY 588 | send_fd(socketfd, -1); 589 | } else { 590 | send_fd(socketfd, STDERR_FILENO); 591 | } 592 | 593 | // Number of command line arguments 594 | write_int(socketfd, argc); 595 | 596 | // Command line arguments 597 | int i; 598 | for (i = 0; i < argc; i++) { 599 | write_string(socketfd, argv[i]); 600 | } 601 | 602 | // Wait for acknowledgement from daemon 603 | read_int(socketfd); 604 | 605 | if (atty & ATTY_IN) { 606 | setup_sighandlers(); 607 | pump_stdin_async(ptmx); 608 | } 609 | if (atty & ATTY_OUT) { 610 | pump_stdout_blocking(ptmx); 611 | } 612 | 613 | // Get the exit code 614 | int code = read_int(socketfd); 615 | close(socketfd); 616 | ALOGD("client exited %d", code); 617 | 618 | return code; 619 | } 620 | -------------------------------------------------------------------------------- /pts.c: -------------------------------------------------------------------------------- 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.c 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 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "pts.h" 34 | 35 | /** 36 | * Helper functions 37 | */ 38 | // Ensures all the data is written out 39 | static int write_blocking(int fd, char* buf, size_t bufsz) { 40 | ssize_t ret, written; 41 | 42 | written = 0; 43 | do { 44 | ret = write(fd, buf + written, bufsz - written); 45 | if (ret == -1) return -1; 46 | written += ret; 47 | } while (written < (ssize_t)bufsz); 48 | 49 | return 0; 50 | } 51 | 52 | /** 53 | * Pump data from input FD to output FD. If close_output is 54 | * true, then close the output FD when we're done. 55 | */ 56 | static void pump_ex(int input, int output, int close_output) { 57 | char buf[4096]; 58 | int len; 59 | while ((len = read(input, buf, 4096)) > 0) { 60 | if (write_blocking(output, buf, len) == -1) break; 61 | } 62 | close(input); 63 | if (close_output) close(output); 64 | } 65 | 66 | /** 67 | * Pump data from input FD to output FD. Will close the 68 | * output FD when done. 69 | */ 70 | static void pump(int input, int output) { 71 | pump_ex(input, output, 1); 72 | } 73 | 74 | static void* pump_thread(void* data) { 75 | int* files = (int*)data; 76 | int input = files[0]; 77 | int output = files[1]; 78 | pump(input, output); 79 | free(data); 80 | return NULL; 81 | } 82 | 83 | static void pump_async(int input, int output) { 84 | pthread_t writer; 85 | int* files = (int*)malloc(sizeof(int) * 2); 86 | if (files == NULL) { 87 | exit(-1); 88 | } 89 | files[0] = input; 90 | files[1] = output; 91 | pthread_create(&writer, NULL, pump_thread, files); 92 | } 93 | 94 | /** 95 | * pts_open 96 | * 97 | * Opens a pts device and returns the name of the slave tty device. 98 | * 99 | * Arguments 100 | * slave_name the name of the slave device 101 | * slave_name_size the size of the buffer passed via slave_name 102 | * 103 | * Return Values 104 | * on failure either -2 or -1 (errno set) is returned. 105 | * on success, the file descriptor of the master device is returned. 106 | */ 107 | int pts_open(char* slave_name, size_t slave_name_size) { 108 | int fdm; 109 | char sn_tmp[slave_name_size]; 110 | 111 | // Open master ptmx device 112 | fdm = open("/dev/ptmx", O_RDWR); 113 | if (fdm == -1) return -1; 114 | 115 | // Get the slave name 116 | if (ptsname_r(fdm, sn_tmp, slave_name_size) != 0) { 117 | close(fdm); 118 | return -2; 119 | } 120 | 121 | if (strlcpy(slave_name, sn_tmp, slave_name_size) >= slave_name_size) { 122 | return -1; 123 | } 124 | 125 | // Grant, then unlock 126 | if (grantpt(fdm) == -1) { 127 | close(fdm); 128 | return -1; 129 | } 130 | if (unlockpt(fdm) == -1) { 131 | close(fdm); 132 | return -1; 133 | } 134 | 135 | return fdm; 136 | } 137 | 138 | // Stores the previous termios of stdin 139 | static struct termios old_stdin; 140 | static int stdin_is_raw = 0; 141 | 142 | /** 143 | * set_stdin_raw 144 | * 145 | * Changes stdin to raw unbuffered mode, disables echo, 146 | * auto carriage return, etc. 147 | * 148 | * Return Value 149 | * on failure -1, and errno is set 150 | * on success 0 151 | */ 152 | int set_stdin_raw(void) { 153 | struct termios new_termios; 154 | 155 | // Save the current stdin termios 156 | if (tcgetattr(STDIN_FILENO, &old_stdin) < 0) { 157 | return -1; 158 | } 159 | 160 | // Start from the current settings 161 | new_termios = old_stdin; 162 | 163 | // Make the terminal like an SSH or telnet client 164 | new_termios.c_iflag |= IGNPAR; 165 | new_termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF); 166 | new_termios.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL); 167 | new_termios.c_oflag &= ~OPOST; 168 | new_termios.c_cc[VMIN] = 1; 169 | new_termios.c_cc[VTIME] = 0; 170 | 171 | if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_termios) < 0) { 172 | return -1; 173 | } 174 | 175 | stdin_is_raw = 1; 176 | 177 | return 0; 178 | } 179 | 180 | /** 181 | * restore_stdin 182 | * 183 | * Restore termios on stdin to the state it was before 184 | * set_stdin_raw() was called. If set_stdin_raw() was 185 | * never called, does nothing and doesn't return an error. 186 | * 187 | * This function is async-safe. 188 | * 189 | * Return Value 190 | * on failure, -1 and errno is set 191 | * on success, 0 192 | */ 193 | int restore_stdin(void) { 194 | if (!stdin_is_raw) return 0; 195 | 196 | if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_stdin) < 0) { 197 | return -1; 198 | } 199 | 200 | stdin_is_raw = 0; 201 | 202 | return 0; 203 | } 204 | 205 | // Flag indicating whether the sigwinch watcher should terminate. 206 | static volatile int closing_time = 0; 207 | 208 | /** 209 | * Thread process. Wait for a SIGWINCH to be received, then update 210 | * the terminal size. 211 | */ 212 | static void* watch_sigwinch(void* data) { 213 | sigset_t winch; 214 | int sig; 215 | int master = ((int*)data)[0]; 216 | int slave = ((int*)data)[1]; 217 | 218 | sigemptyset(&winch); 219 | sigaddset(&winch, SIGWINCH); 220 | 221 | do { 222 | // Wait for a SIGWINCH 223 | sigwait(&winch, &sig); 224 | 225 | if (closing_time) break; 226 | 227 | // Get the new terminal size 228 | struct winsize w; 229 | if (ioctl(master, TIOCGWINSZ, &w) == -1) { 230 | continue; 231 | } 232 | 233 | // Set the new terminal size 234 | ioctl(slave, TIOCSWINSZ, &w); 235 | 236 | } while (1); 237 | 238 | free(data); 239 | return NULL; 240 | } 241 | 242 | /** 243 | * watch_sigwinch_async 244 | * 245 | * After calling this function, if the application receives 246 | * SIGWINCH, the terminal window size will be read from 247 | * "input" and set on "output". 248 | * 249 | * NOTE: This function blocks SIGWINCH and spawns a thread. 250 | * NOTE 2: This function must be called before any of the 251 | * pump functions. 252 | * 253 | * Arguments 254 | * master A file descriptor of the TTY window size to follow 255 | * slave A file descriptor of the TTY window size which is 256 | * to be set on SIGWINCH 257 | * 258 | * Return Value 259 | * on failure, -1 and errno will be set. In this case, no 260 | * thread has been spawned and SIGWINCH will not be 261 | * blocked. 262 | * on success, 0 263 | */ 264 | int watch_sigwinch_async(int master, int slave) { 265 | pthread_t watcher; 266 | int* files = (int*)malloc(sizeof(int) * 2); 267 | if (files == NULL) { 268 | return -1; 269 | } 270 | 271 | // Block SIGWINCH so sigwait can later receive it 272 | sigset_t winch; 273 | sigemptyset(&winch); 274 | sigaddset(&winch, SIGWINCH); 275 | if (sigprocmask(SIG_BLOCK, &winch, NULL) == -1) { 276 | free(files); 277 | return -1; 278 | } 279 | 280 | // Initialize some variables, then start the thread 281 | closing_time = 0; 282 | files[0] = master; 283 | files[1] = slave; 284 | int ret = pthread_create(&watcher, NULL, &watch_sigwinch, files); 285 | if (ret != 0) { 286 | free(files); 287 | errno = ret; 288 | return -1; 289 | } 290 | 291 | // Set the initial terminal size 292 | raise(SIGWINCH); 293 | return 0; 294 | } 295 | 296 | /** 297 | * watch_sigwinch_cleanup 298 | * 299 | * Cause the SIGWINCH watcher thread to terminate 300 | */ 301 | void watch_sigwinch_cleanup(void) { 302 | closing_time = 1; 303 | raise(SIGWINCH); 304 | } 305 | 306 | /** 307 | * pump_stdin_async 308 | * 309 | * Forward data from STDIN to the given FD 310 | * in a seperate thread 311 | */ 312 | void pump_stdin_async(int outfd) { 313 | // Put stdin into raw mode 314 | set_stdin_raw(); 315 | 316 | // Pump data from stdin to the PTY 317 | pump_async(STDIN_FILENO, outfd); 318 | } 319 | 320 | /** 321 | * pump_stdout_blocking 322 | * 323 | * Forward data from the FD to STDOUT. 324 | * Returns when the remote end of the FD closes. 325 | * 326 | * Before returning, restores stdin settings. 327 | */ 328 | void pump_stdout_blocking(int infd) { 329 | // Pump data from stdout to PTY 330 | pump_ex(infd, STDOUT_FILENO, 0 /* Don't close output when done */); 331 | 332 | // Cleanup 333 | restore_stdin(); 334 | watch_sigwinch_cleanup(); 335 | } 336 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /su.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright 2010, Adam Shanks (@ChainsDD) 3 | ** Copyright 2008, Zinx Verituse (@zinxv) 4 | ** Copyright 2017-2018, The LineageOS Project 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 | 27 | #include 28 | #include 29 | #include 30 | 31 | #include "binder/pm-wrapper.h" 32 | #include "su.h" 33 | #include "utils.h" 34 | 35 | extern int is_daemon; 36 | extern int daemon_from_uid; 37 | extern int daemon_from_pid; 38 | 39 | int fork_zero_fucks() { 40 | int pid = fork(); 41 | if (pid) { 42 | int status; 43 | waitpid(pid, &status, 0); 44 | return pid; 45 | } else { 46 | if ((pid = fork())) exit(0); 47 | return 0; 48 | } 49 | } 50 | 51 | static int from_init(struct su_initiator* from) { 52 | char path[PATH_MAX], exe[PATH_MAX]; 53 | char args[4096], *argv0, *argv_rest; 54 | int fd; 55 | ssize_t len; 56 | int i; 57 | int err; 58 | 59 | from->uid = getuid(); 60 | from->pid = getppid(); 61 | 62 | if (is_daemon) { 63 | from->uid = daemon_from_uid; 64 | from->pid = daemon_from_pid; 65 | } 66 | 67 | /* Get the command line */ 68 | snprintf(path, sizeof(path), "/proc/%d/cmdline", from->pid); 69 | fd = open(path, O_RDONLY); 70 | if (fd < 0) { 71 | PLOGE("Opening command line"); 72 | return -1; 73 | } 74 | len = read(fd, args, sizeof(args)); 75 | err = errno; 76 | close(fd); 77 | if (len < 0 || len == sizeof(args)) { 78 | PLOGEV("Reading command line", err); 79 | return -1; 80 | } 81 | 82 | argv0 = args; 83 | argv_rest = NULL; 84 | for (i = 0; i < len; i++) { 85 | if (args[i] == '\0') { 86 | if (!argv_rest) { 87 | argv_rest = &args[i + 1]; 88 | } else { 89 | args[i] = ' '; 90 | } 91 | } 92 | } 93 | args[len] = '\0'; 94 | 95 | if (argv_rest) { 96 | if (strlcpy(from->args, argv_rest, sizeof(from->args)) >= sizeof(from->args)) { 97 | ALOGE("argument too long"); 98 | return -1; 99 | } 100 | } else { 101 | from->args[0] = '\0'; 102 | } 103 | 104 | /* If this isn't app_process, use the real path instead of argv[0] */ 105 | snprintf(path, sizeof(path), "/proc/%d/exe", from->pid); 106 | len = readlink(path, exe, sizeof(exe)); 107 | if (len < 0) { 108 | PLOGE("Getting exe path"); 109 | return -1; 110 | } 111 | exe[len] = '\0'; 112 | if (strcmp(exe, "/system/bin/app_process") != 0) { 113 | argv0 = exe; 114 | } 115 | 116 | if (strlcpy(from->bin, argv0, sizeof(from->bin)) >= sizeof(from->bin)) { 117 | ALOGE("binary path too long"); 118 | return -1; 119 | } 120 | 121 | struct passwd* pw; 122 | pw = getpwuid(from->uid); 123 | if (pw && pw->pw_name) { 124 | if (strlcpy(from->name, pw->pw_name, sizeof(from->name)) >= sizeof(from->name)) { 125 | ALOGE("name too long"); 126 | return -1; 127 | } 128 | } 129 | 130 | return 0; 131 | } 132 | 133 | static void populate_environment(const struct su_context* ctx) { 134 | struct passwd* pw; 135 | 136 | if (ctx->to.keepenv) return; 137 | 138 | pw = getpwuid(ctx->to.uid); 139 | if (pw) { 140 | setenv("HOME", pw->pw_dir, 1); 141 | if (ctx->to.shell) 142 | setenv("SHELL", ctx->to.shell, 1); 143 | else 144 | setenv("SHELL", DEFAULT_SHELL, 1); 145 | if (ctx->to.login || ctx->to.uid) { 146 | setenv("USER", pw->pw_name, 1); 147 | setenv("LOGNAME", pw->pw_name, 1); 148 | } 149 | } 150 | } 151 | 152 | void set_identity(unsigned int uid) { 153 | /* 154 | * Set effective uid back to root, otherwise setres[ug]id will fail 155 | * if uid isn't root. 156 | */ 157 | if (seteuid(0)) { 158 | PLOGE("seteuid (root)"); 159 | exit(EXIT_FAILURE); 160 | } 161 | if (setresgid(uid, uid, uid)) { 162 | PLOGE("setresgid (%u)", uid); 163 | exit(EXIT_FAILURE); 164 | } 165 | if (setresuid(uid, uid, uid)) { 166 | PLOGE("setresuid (%u)", uid); 167 | exit(EXIT_FAILURE); 168 | } 169 | } 170 | 171 | static void usage(int status) { 172 | FILE* stream = (status == EXIT_SUCCESS) ? stdout : stderr; 173 | 174 | fprintf(stream, 175 | "Usage: su [options] [--] [-] [LOGIN] [--] [args...]\n\n" 176 | "Options:\n" 177 | " --daemon start the su daemon agent\n" 178 | " -c, --command COMMAND pass COMMAND to the invoked shell\n" 179 | " -h, --help display this help message and exit\n" 180 | " -, -l, --login pretend the shell to be a login shell\n" 181 | " -m, -p,\n" 182 | " --preserve-environment do not change environment variables\n" 183 | " -s, --shell SHELL use SHELL instead of the default " DEFAULT_SHELL 184 | "\n" 185 | " -v, --version display version number and exit\n" 186 | " -V display version code and exit,\n" 187 | " this is used almost exclusively by Superuser.apk\n"); 188 | exit(status); 189 | } 190 | 191 | static __attribute__((noreturn)) void deny(struct su_context* ctx) { 192 | char* cmd = get_command(&ctx->to); 193 | ALOGW("request rejected (%u->%u %s)", ctx->from.uid, ctx->to.uid, cmd); 194 | fprintf(stderr, "%s\n", strerror(EACCES)); 195 | exit(EXIT_FAILURE); 196 | } 197 | 198 | static __attribute__((noreturn)) void allow(struct su_context* ctx, const char* packageName) { 199 | char* arg0; 200 | int argc, err; 201 | 202 | umask(ctx->umask); 203 | 204 | char* binary; 205 | argc = ctx->to.optind; 206 | if (ctx->to.command) { 207 | binary = ctx->to.shell; 208 | ctx->to.argv[--argc] = ctx->to.command; 209 | ctx->to.argv[--argc] = "-c"; 210 | } else if (ctx->to.shell) { 211 | binary = ctx->to.shell; 212 | } else { 213 | if (ctx->to.argv[argc]) { 214 | binary = ctx->to.argv[argc++]; 215 | } else { 216 | binary = DEFAULT_SHELL; 217 | } 218 | } 219 | 220 | arg0 = strrchr(binary, '/'); 221 | arg0 = (arg0) ? arg0 + 1 : binary; 222 | if (ctx->to.login) { 223 | int s = strlen(arg0) + 2; 224 | char* p = malloc(s); 225 | 226 | if (!p) exit(EXIT_FAILURE); 227 | 228 | *p = '-'; 229 | strcpy(p + 1, arg0); 230 | arg0 = p; 231 | } 232 | 233 | populate_environment(ctx); 234 | set_identity(ctx->to.uid); 235 | 236 | #define PARG(arg) \ 237 | (argc + (arg) < ctx->to.argc) ? " " : "", \ 238 | (argc + (arg) < ctx->to.argc) ? ctx->to.argv[argc + (arg)] : "" 239 | 240 | ALOGD("%u %s executing %u %s using binary %s : %s%s%s%s%s%s%s%s%s%s%s%s%s%s", ctx->from.uid, 241 | ctx->from.bin, ctx->to.uid, get_command(&ctx->to), binary, arg0, PARG(0), PARG(1), 242 | PARG(2), PARG(3), PARG(4), PARG(5), (ctx->to.optind + 6 < ctx->to.argc) ? " ..." : ""); 243 | 244 | ctx->to.argv[--argc] = arg0; 245 | 246 | int pid = fork(); 247 | if (!pid) { 248 | execvp(binary, ctx->to.argv + argc); 249 | err = errno; 250 | PLOGE("exec"); 251 | fprintf(stderr, "Cannot execute %s: %s\n", binary, strerror(err)); 252 | exit(EXIT_FAILURE); 253 | } else { 254 | int status, code; 255 | 256 | ALOGD("Waiting for pid %d.", pid); 257 | waitpid(pid, &status, 0); 258 | ALOGD("pid %d returned %d.", pid, status); 259 | code = WIFSIGNALED(status) ? WTERMSIG(status) + 128 : WEXITSTATUS(status); 260 | 261 | if (packageName) { 262 | appops_finish_op_su(ctx->from.uid, packageName); 263 | } 264 | exit(code); 265 | } 266 | } 267 | 268 | int access_disabled(const struct su_initiator* from) { 269 | char lineage_version[PROPERTY_VALUE_MAX]; 270 | char build_type[PROPERTY_VALUE_MAX]; 271 | int enabled; 272 | 273 | /* Only allow su on Lineage builds */ 274 | property_get("ro.lineage.version", lineage_version, ""); 275 | if (!strcmp(lineage_version, "")) { 276 | ALOGE("Root access disabled on Non-Lineage builds"); 277 | return 1; 278 | } 279 | 280 | /* Only allow su on debuggable builds */ 281 | if (!property_get_bool("ro.debuggable", false)) { 282 | ALOGE("Root access is disabled on non-debug builds"); 283 | return 1; 284 | } 285 | 286 | /* Enforce persist.sys.root_access on non-eng builds for apps */ 287 | enabled = property_get_int32("persist.sys.root_access", 2); 288 | property_get("ro.build.type", build_type, ""); 289 | if (strcmp("eng", build_type) != 0 && from->uid != AID_SHELL && from->uid != AID_ROOT && 290 | (enabled & LINEAGE_ROOT_ACCESS_APPS_ONLY) != LINEAGE_ROOT_ACCESS_APPS_ONLY) { 291 | ALOGE( 292 | "Apps root access is disabled by system setting - " 293 | "enable it under settings -> developer options"); 294 | return 1; 295 | } 296 | 297 | /* disallow su in a shell if appropriate */ 298 | if (from->uid == AID_SHELL && 299 | (enabled & LINEAGE_ROOT_ACCESS_ADB_ONLY) != LINEAGE_ROOT_ACCESS_ADB_ONLY) { 300 | ALOGE( 301 | "Shell root access is disabled by a system setting - " 302 | "enable it under settings -> developer options"); 303 | return 1; 304 | } 305 | 306 | return 0; 307 | } 308 | 309 | int main(int argc, char* argv[]) { 310 | if (getuid() != geteuid()) { 311 | ALOGE("must not be a setuid binary"); 312 | return 1; 313 | } 314 | 315 | return su_main(argc, argv, 1); 316 | } 317 | 318 | int su_main(int argc, char* argv[], int need_client) { 319 | // start up in daemon mode if prompted 320 | if (argc == 2 && strcmp(argv[1], "--daemon") == 0) { 321 | return run_daemon(); 322 | } 323 | 324 | int ppid = getppid(); 325 | 326 | // Sanitize all secure environment variables (from linker_environ.c in AOSP linker). 327 | /* The same list than GLibc at this point */ 328 | static const char* const unsec_vars[] = { 329 | "GCONV_PATH", 330 | "GETCONF_DIR", 331 | "HOSTALIASES", 332 | "LD_AUDIT", 333 | "LD_DEBUG", 334 | "LD_DEBUG_OUTPUT", 335 | "LD_DYNAMIC_WEAK", 336 | "LD_LIBRARY_PATH", 337 | "LD_ORIGIN_PATH", 338 | "LD_PRELOAD", 339 | "LD_PROFILE", 340 | "LD_SHOW_AUXV", 341 | "LD_USE_LOAD_BIAS", 342 | "LOCALDOMAIN", 343 | "LOCPATH", 344 | "MALLOC_TRACE", 345 | "MALLOC_CHECK_", 346 | "NIS_PATH", 347 | "NLSPATH", 348 | "RESOLV_HOST_CONF", 349 | "RES_OPTIONS", 350 | "TMPDIR", 351 | "TZDIR", 352 | "LD_AOUT_LIBRARY_PATH", 353 | "LD_AOUT_PRELOAD", 354 | // not listed in linker, used due to system() call 355 | "IFS", 356 | }; 357 | const char* const* cp = unsec_vars; 358 | const char* const* endp = cp + sizeof(unsec_vars) / sizeof(unsec_vars[0]); 359 | while (cp < endp) { 360 | unsetenv(*cp); 361 | cp++; 362 | } 363 | 364 | ALOGD("su invoked."); 365 | 366 | struct su_context ctx = { 367 | .from = 368 | { 369 | .pid = -1, 370 | .uid = 0, 371 | .bin = "", 372 | .args = "", 373 | .name = "", 374 | }, 375 | .to = 376 | { 377 | .uid = AID_ROOT, 378 | .login = 0, 379 | .keepenv = 0, 380 | .shell = NULL, 381 | .command = NULL, 382 | .argv = argv, 383 | .argc = argc, 384 | .optind = 0, 385 | .name = "", 386 | }, 387 | }; 388 | int c; 389 | struct option long_opts[] = { 390 | {"command", required_argument, NULL, 'c'}, 391 | {"help", no_argument, NULL, 'h'}, 392 | {"login", no_argument, NULL, 'l'}, 393 | {"preserve-environment", no_argument, NULL, 'p'}, 394 | {"shell", required_argument, NULL, 's'}, 395 | {"version", no_argument, NULL, 'v'}, 396 | {NULL, 0, NULL, 0}, 397 | }; 398 | 399 | while ((c = getopt_long(argc, argv, "+c:hlmps:Vv", long_opts, NULL)) != -1) { 400 | switch (c) { 401 | case 'c': 402 | ctx.to.shell = DEFAULT_SHELL; 403 | ctx.to.command = optarg; 404 | break; 405 | case 'h': 406 | usage(EXIT_SUCCESS); 407 | break; 408 | case 'l': 409 | ctx.to.login = 1; 410 | break; 411 | case 'm': 412 | case 'p': 413 | ctx.to.keepenv = 1; 414 | break; 415 | case 's': 416 | ctx.to.shell = optarg; 417 | break; 418 | case 'V': 419 | printf("%d\n", VERSION_CODE); 420 | exit(EXIT_SUCCESS); 421 | case 'v': 422 | printf("%s\n", VERSION); 423 | exit(EXIT_SUCCESS); 424 | default: 425 | /* Bionic getopt_long doesn't terminate its error output by newline */ 426 | fprintf(stderr, "\n"); 427 | usage(2); 428 | } 429 | } 430 | 431 | if (need_client) { 432 | // attempt to connect to daemon... 433 | ALOGD("starting daemon client %d %d", getuid(), geteuid()); 434 | return connect_daemon(argc, argv, ppid); 435 | } 436 | 437 | if (optind < argc && !strcmp(argv[optind], "-")) { 438 | ctx.to.login = 1; 439 | optind++; 440 | } 441 | /* username or uid */ 442 | if (optind < argc && strcmp(argv[optind], "--") != 0) { 443 | struct passwd* pw; 444 | pw = getpwnam(argv[optind]); 445 | if (!pw) { 446 | char* endptr; 447 | 448 | /* It seems we shouldn't do this at all */ 449 | errno = 0; 450 | ctx.to.uid = strtoul(argv[optind], &endptr, 10); 451 | if (errno || *endptr) { 452 | ALOGE("Unknown id: %s\n", argv[optind]); 453 | fprintf(stderr, "Unknown id: %s\n", argv[optind]); 454 | exit(EXIT_FAILURE); 455 | } 456 | } else { 457 | ctx.to.uid = pw->pw_uid; 458 | if (pw->pw_name) { 459 | if (strlcpy(ctx.to.name, pw->pw_name, sizeof(ctx.to.name)) >= sizeof(ctx.to.name)) { 460 | ALOGE("name too long"); 461 | exit(EXIT_FAILURE); 462 | } 463 | } 464 | } 465 | optind++; 466 | } 467 | if (optind < argc && !strcmp(argv[optind], "--")) { 468 | optind++; 469 | } 470 | ctx.to.optind = optind; 471 | 472 | if (from_init(&ctx.from) < 0) { 473 | deny(&ctx); 474 | } 475 | 476 | ALOGE("SU from: %s", ctx.from.name); 477 | 478 | if (ctx.from.uid == AID_ROOT) { 479 | ALOGD("Allowing root."); 480 | allow(&ctx, NULL); 481 | } 482 | 483 | // check if superuser is disabled completely 484 | if (access_disabled(&ctx.from)) { 485 | ALOGD("access_disabled"); 486 | deny(&ctx); 487 | } 488 | 489 | // autogrant shell at this point 490 | if (ctx.from.uid == AID_SHELL) { 491 | ALOGD("Allowing shell."); 492 | allow(&ctx, NULL); 493 | } 494 | 495 | char* packageName = resolve_package_name(ctx.from.uid); 496 | if (packageName) { 497 | if (!appops_start_op_su(ctx.from.uid, packageName)) { 498 | ALOGD("Allowing via appops."); 499 | allow(&ctx, packageName); 500 | } 501 | free(packageName); 502 | } 503 | 504 | ALOGE("Allow chain exhausted, denying request"); 505 | deny(&ctx); 506 | } 507 | -------------------------------------------------------------------------------- /su.h: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright 2010, Adam Shanks (@ChainsDD) 3 | ** Copyright 2008, Zinx Verituse (@zinxv) 4 | ** Copyright 2017-2018, The LineageOS Project 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 | #ifdef LOG_TAG 23 | #undef LOG_TAG 24 | #endif 25 | #define LOG_TAG "su" 26 | 27 | // Lineage-specific behavior 28 | #define LINEAGE_ROOT_ACCESS_DISABLED 0 29 | #define LINEAGE_ROOT_ACCESS_APPS_ONLY 1 30 | #define LINEAGE_ROOT_ACCESS_ADB_ONLY 2 31 | #define LINEAGE_ROOT_ACCESS_APPS_AND_ADB 3 32 | 33 | #define DAEMON_SOCKET_PATH "/dev/socket/su-daemon/" 34 | 35 | #define DEFAULT_SHELL "/system/bin/sh" 36 | 37 | #define xstr(a) str(a) 38 | #define str(a) #a 39 | 40 | #ifndef VERSION_CODE 41 | #define VERSION_CODE 16 42 | #endif 43 | #define VERSION xstr(VERSION_CODE) " cm-su" 44 | 45 | #define PROTO_VERSION 1 46 | 47 | struct su_initiator { 48 | pid_t pid; 49 | unsigned uid; 50 | unsigned user; 51 | char name[64]; 52 | char bin[PATH_MAX]; 53 | char args[4096]; 54 | }; 55 | 56 | struct su_request { 57 | unsigned uid; 58 | char name[64]; 59 | int login; 60 | int keepenv; 61 | char* shell; 62 | char* command; 63 | char** argv; 64 | int argc; 65 | int optind; 66 | }; 67 | 68 | struct su_context { 69 | struct su_initiator from; 70 | struct su_request to; 71 | mode_t umask; 72 | char sock_path[PATH_MAX]; 73 | }; 74 | 75 | typedef enum { 76 | INTERACTIVE = 0, 77 | DENY = 1, 78 | ALLOW = 2, 79 | } policy_t; 80 | 81 | extern void set_identity(unsigned int uid); 82 | 83 | static inline char* get_command(const struct su_request* to) { 84 | if (to->command) return to->command; 85 | if (to->shell) return to->shell; 86 | char* ret = to->argv[to->optind]; 87 | if (ret) return ret; 88 | return DEFAULT_SHELL; 89 | } 90 | 91 | int appops_start_op_su(int uid, const char* pkgName); 92 | int appops_finish_op_su(int uid, const char* pkgName); 93 | 94 | int run_daemon(); 95 | int connect_daemon(int argc, char* argv[], int ppid); 96 | int su_main(int argc, char* argv[], int need_client); 97 | // for when you give zero fucks about the state of the child process. 98 | // this version of fork understands you don't care about the child. 99 | // deadbeat dad fork. 100 | int fork_zero_fucks(); 101 | 102 | #ifndef LOG_NDEBUG 103 | #define LOG_NDEBUG 1 104 | #endif 105 | 106 | #include 107 | #include 108 | #define PLOGE(fmt, args...) ALOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno)) 109 | #define PLOGEV(fmt, err, args...) ALOGE(fmt " failed with %d: %s", ##args, err, strerror(err)) 110 | 111 | #endif 112 | -------------------------------------------------------------------------------- /superuser.rc: -------------------------------------------------------------------------------- 1 | # su daemon 2 | service su_daemon /system/xbin/su --daemon 3 | user root 4 | group root 5 | disabled 6 | seclabel u:r:sudaemon:s0 7 | 8 | on property:persist.sys.root_access=0 9 | stop su_daemon 10 | 11 | on property:persist.sys.root_access=2 12 | stop su_daemon 13 | 14 | on property:persist.sys.root_access=1 15 | start su_daemon 16 | 17 | on property:persist.sys.root_access=3 18 | start su_daemon 19 | -------------------------------------------------------------------------------- /utils.c: -------------------------------------------------------------------------------- 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 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "utils.h" 23 | 24 | /* reads a file, making sure it is terminated with \n \0 */ 25 | char* read_file(const char* fn) { 26 | struct stat st; 27 | char* data = NULL; 28 | 29 | int fd = open(fn, O_RDONLY); 30 | if (fd < 0) return data; 31 | 32 | if (fstat(fd, &st)) goto oops; 33 | 34 | data = malloc(st.st_size + 2); 35 | if (!data) goto oops; 36 | 37 | if (read(fd, data, st.st_size) != st.st_size) goto oops; 38 | close(fd); 39 | data[st.st_size] = '\n'; 40 | data[st.st_size + 1] = 0; 41 | return data; 42 | 43 | oops: 44 | close(fd); 45 | if (data) free(data); 46 | return NULL; 47 | } 48 | -------------------------------------------------------------------------------- /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 | /* reads a file, making sure it is terminated with \n \0 */ 21 | extern char* read_file(const char* fn); 22 | 23 | #endif 24 | --------------------------------------------------------------------------------