--- /dev/null
+/*
+** Copyright 2010, Adam Shanks (@ChainsDD)
+** Copyright 2008, Zinx Verituse (@zinxv)
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#include <unistd.h>
+#include <android_runtime/ActivityManager.h>
+#include <binder/IBinder.h>
+#include <binder/IServiceManager.h>
+#include <binder/Parcel.h>
+#include <utils/String8.h>
+#include <assert.h>
+
+extern "C" {
+#include "su.h"
+#include <private/android_filesystem_config.h>
+#include <cutils/properties.h>
+}
+
+using namespace android;
+
+static const int BROADCAST_INTENT_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION + 13;
+
+static const int NULL_TYPE_ID = 0;
+
+static const int VAL_STRING = 0;
+static const int VAL_INTEGER = 1;
+
+static const int START_SUCCESS = 0;
+
+int send_intent(struct su_initiator *from, struct su_request *to, const char *socket_path, int allow, int type)
+{
+ char sdk_version_prop[PROPERTY_VALUE_MAX] = "0";
+ property_get("ro.build.version.sdk", sdk_version_prop, "0");
+
+ int sdk_version = atoi(sdk_version_prop);
+
+ sp<IServiceManager> sm = defaultServiceManager();
+ sp<IBinder> am = sm->checkService(String16("activity"));
+ assert(am != NULL);
+
+ Parcel data, reply;
+ data.writeInterfaceToken(String16("android.app.IActivityManager"));
+
+ data.writeStrongBinder(NULL); /* caller */
+
+ /* intent */
+ if (type == 0) {
+ data.writeString16(String16("com.noshufou.android.su.REQUEST")); /* action */
+ } else {
+ data.writeString16(String16("com.noshufou.android.su.RESULT")); /* action */
+ }
+ data.writeInt32(NULL_TYPE_ID); /* Uri - data */
+ data.writeString16(NULL, 0); /* type */
+ data.writeInt32(0); /* flags */
+ if (sdk_version >= 4) {
+ // added in donut
+ data.writeString16(NULL, 0); /* package name - DONUT ONLY, NOT IN CUPCAKE. */
+ }
+ data.writeString16(NULL, 0); /* ComponentName - package */
+ data.writeInt32(0); /* Categories - size */
+ if (sdk_version >= 7) {
+ // added in eclair rev 7
+ data.writeInt32(0);
+ }
+ if (sdk_version >= 15) {
+ // added in IceCreamSandwich 4.0.3
+ data.writeInt32(0); /* Selector */
+ }
+ { /* Extras */
+ data.writeInt32(-1); /* dummy, will hold length */
+ int oldPos = data.dataPosition();
+ data.writeInt32(0x4C444E42); // 'B' 'N' 'D' 'L'
+ { /* writeMapInternal */
+ data.writeInt32(7); /* writeMapInternal - size */
+
+ data.writeInt32(VAL_STRING);
+ data.writeString16(String16("caller_uid"));
+ data.writeInt32(VAL_INTEGER);
+ data.writeInt32(from->uid);
+
+ data.writeInt32(VAL_STRING);
+ data.writeString16(String16("caller_bin"));
+ data.writeInt32(VAL_STRING);
+ data.writeString16(String16(from->bin));
+
+ data.writeInt32(VAL_STRING);
+ data.writeString16(String16("desired_uid"));
+ data.writeInt32(VAL_INTEGER);
+ data.writeInt32(to->uid);
+
+ data.writeInt32(VAL_STRING);
+ data.writeString16(String16("desired_cmd"));
+ data.writeInt32(VAL_STRING);
+ data.writeString16(String16(to->command));
+
+ data.writeInt32(VAL_STRING);
+ data.writeString16(String16("socket"));
+ data.writeInt32(VAL_STRING);
+ data.writeString16(String16(socket_path));
+
+ data.writeInt32(VAL_STRING);
+ data.writeString16(String16("allow"));
+ data.writeInt32(VAL_INTEGER);
+ data.writeInt32(allow);
+
+ data.writeInt32(VAL_STRING);
+ data.writeString16(String16("version_code"));
+ data.writeInt32(VAL_INTEGER);
+ data.writeInt32(VERSION_CODE);
+ }
+ int newPos = data.dataPosition();
+ data.setDataPosition(oldPos - 4);
+ data.writeInt32(newPos - oldPos); /* length */
+ data.setDataPosition(newPos);
+ }
+
+ data.writeString16(NULL, 0); /* resolvedType */
+
+ data.writeInt32(-1); /* Not sure what this is for, but it prevents a warning */
+
+ data.writeStrongBinder(NULL); /* resultTo */
+ data.writeInt32(-1); /* resultCode */
+ data.writeString16(NULL, 0); /* resultData */
+
+ data.writeInt32(-1); /* resultExtras */
+
+ data.writeString16(String16("com.noshufou.android.su.RESPOND")); /* perm */
+ data.writeInt32(0); /* serialized */
+ data.writeInt32(0); /* sticky */
+ data.writeInt32(-1);
+
+ status_t ret = am->transact(BROADCAST_INTENT_TRANSACTION, data, &reply);
+ if (ret < START_SUCCESS) return -1;
+
+ return 0;
+}
/*
+** Copyright 2010, Adam Shanks (@ChainsDD)
+** Copyright 2008, Zinx Verituse (@zinxv)
**
-** Copyright 2008, The Android Open Source Project
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
+** http://www.apache.org/licenses/LICENSE-2.0
**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
** limitations under the License.
*/
#define LOG_TAG "su"
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
#include <sys/types.h>
-#include <dirent.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <limits.h>
+#include <fcntl.h>
#include <errno.h>
+#include <endian.h>
-#include <unistd.h>
-#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
#include <pwd.h>
#include <private/android_filesystem_config.h>
+#include <cutils/log.h>
-/*
- * SU can be given a specific command to exec. UID _must_ be
- * specified for this (ie argc => 3).
- *
- * Usage:
- * su 1000
- * su 1000 ls -l
- */
-int main(int argc, char **argv)
+#include <sqlite3.h>
+
+#include "su.h"
+
+//extern char* _mktemp(char*); /* mktemp doesn't link right. Don't ask me why. */
+
+extern sqlite3 *database_init();
+extern int database_check(sqlite3*, struct su_initiator*, struct su_request*);
+
+/* Still lazt, will fix this */
+static char *socket_path = NULL;
+static sqlite3 *db = NULL;
+
+static struct su_initiator su_from = {
+ .pid = -1,
+ .uid = 0,
+ .bin = "",
+ .args = "",
+};
+
+static struct su_request su_to = {
+ .uid = AID_ROOT,
+ .command = DEFAULT_COMMAND,
+};
+
+static int from_init(struct su_initiator *from)
{
- struct passwd *pw;
- int uid, gid, myuid;
+ char path[PATH_MAX], exe[PATH_MAX];
+ char args[4096], *argv0, *argv_rest;
+ int fd;
+ ssize_t len;
+ int i;
+ int err;
+
+ from->uid = getuid();
+ from->pid = getppid();
+
+ /* Get the command line */
+ snprintf(path, sizeof(path), "/proc/%u/cmdline", from->pid);
+ fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ PLOGE("Opening command line");
+ return -1;
+ }
+ len = read(fd, args, sizeof(args));
+ err = errno;
+ close(fd);
+ if (len < 0 || len == sizeof(args)) {
+ PLOGEV("Reading command line", err);
+ return -1;
+ }
- if(argc < 2) {
- uid = gid = 0;
+ argv0 = args;
+ argv_rest = NULL;
+ for (i = 0; i < len; i++) {
+ if (args[i] == '\0') {
+ if (!argv_rest) {
+ argv_rest = &args[i+1];
+ } else {
+ args[i] = ' ';
+ }
+ }
+ }
+ args[len] = '\0';
+
+ if (argv_rest) {
+ strncpy(from->args, argv_rest, sizeof(from->args));
+ from->args[sizeof(from->args)-1] = '\0';
} else {
- pw = getpwnam(argv[1]);
+ from->args[0] = '\0';
+ }
- if(pw == 0) {
- uid = gid = atoi(argv[1]);
+ /* If this isn't app_process, use the real path instead of argv[0] */
+ snprintf(path, sizeof(path), "/proc/%u/exe", from->pid);
+ len = readlink(path, exe, sizeof(exe));
+ if (len < 0) {
+ PLOGE("Getting exe path");
+ return -1;
+ }
+ exe[len] = '\0';
+ if (strcmp(exe, "/system/bin/app_process")) {
+ argv0 = exe;
+ }
+
+ strncpy(from->bin, argv0, sizeof(from->bin));
+ from->bin[sizeof(from->bin)-1] = '\0';
+
+ return 0;
+}
+
+static void socket_cleanup(void)
+{
+ unlink(socket_path);
+}
+
+static void cleanup(void)
+{
+ socket_cleanup();
+ if (db) sqlite3_close(db);
+}
+
+static void cleanup_signal(int sig)
+{
+ socket_cleanup();
+ exit(sig);
+}
+
+static int socket_create_temp(void)
+{
+ static char buf[PATH_MAX];
+ int fd;
+
+ struct sockaddr_un sun;
+
+ fd = socket(AF_LOCAL, SOCK_STREAM, 0);
+ if (fd < 0) {
+ PLOGE("socket");
+ return -1;
+ }
+
+ for (;;) {
+ memset(&sun, 0, sizeof(sun));
+ sun.sun_family = AF_LOCAL;
+ strcpy(buf, SOCKET_PATH_TEMPLATE);
+ socket_path = mktemp(buf);
+ snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", socket_path);
+
+ if (bind(fd, (struct sockaddr*)&sun, sizeof(sun)) < 0) {
+ if (errno != EADDRINUSE) {
+ PLOGE("bind");
+ return -1;
+ }
} else {
- uid = pw->pw_uid;
- gid = pw->pw_gid;
+ break;
+ }
+ }
+
+ if (listen(fd, 1) < 0) {
+ PLOGE("listen");
+ return -1;
+ }
+
+ return fd;
+}
+
+static int socket_accept(int serv_fd)
+{
+ struct timeval tv;
+ fd_set fds;
+ int fd;
+
+ /* Wait 20 seconds for a connection, then give up. */
+ tv.tv_sec = 20;
+ tv.tv_usec = 0;
+ FD_ZERO(&fds);
+ FD_SET(serv_fd, &fds);
+ if (select(serv_fd + 1, &fds, NULL, NULL, &tv) < 1) {
+ PLOGE("select");
+ return -1;
+ }
+
+ fd = accept(serv_fd, NULL, NULL);
+ if (fd < 0) {
+ PLOGE("accept");
+ return -1;
+ }
+
+ return fd;
+}
+
+static int socket_receive_result(int serv_fd, char *result, ssize_t result_len)
+{
+ ssize_t len;
+
+ for (;;) {
+ int fd = socket_accept(serv_fd);
+ if (fd < 0)
+ return -1;
+
+ len = read(fd, result, result_len-1);
+ if (len < 0) {
+ PLOGE("read(result)");
+ return -1;
+ }
+
+ if (len > 0) {
+ break;
}
}
-#if 0
- /* Until we have something better, only root and the shell can use su. */
- myuid = getuid();
- if (myuid != AID_ROOT && myuid != AID_SHELL) {
- fprintf(stderr,"su: uid %d not allowed to su\n", myuid);
- return 1;
- }
-#endif
- if(setgid(gid) || setuid(uid)) {
- fprintf(stderr,"su: permission denied\n");
- return 1;
- }
-
- /* User specified command for exec. */
- if (argc == 3 ) {
- if (execlp(argv[2], argv[2], NULL) < 0) {
- fprintf(stderr, "su: exec failed for %s Error:%s\n", argv[2],
- strerror(errno));
- return -errno;
+
+ result[len] = '\0';
+
+ return 0;
+}
+
+static void usage(void)
+{
+ printf("Usage: su [options] [LOGIN]\n\n");
+ printf("Options:\n");
+ printf(" -c, --command COMMAND pass COMMAND to the invoked shell\n");
+ printf(" -h, --help display this help message and exit\n");
+ printf(" -, -l, --login make the shell a login shell\n");
+ // I'll look more into this to figure out what it's about,
+ // maybe implement it later
+// printf(" -m, -p,\n");
+// printf(" --preserve-environment do not reset environment variables, and\n");
+// printf(" keep the same shell\n");
+ printf(" -s, --shell SHELL use SHELL instead of the default in passwd\n");
+ printf(" -v, --version display version number and exit\n");
+ printf(" -V display version code and exit. this is\n");
+ printf(" used almost exclusively by Superuser.apk\n");
+ exit(EXIT_SUCCESS);
+}
+
+static void deny(void)
+{
+ struct su_initiator *from = &su_from;
+ struct su_request *to = &su_to;
+
+ send_intent(&su_from, &su_to, "", 0, 1);
+ LOGW("request rejected (%u->%u %s)", from->uid, to->uid, to->command);
+ fprintf(stderr, "%s\n", strerror(EACCES));
+ exit(EXIT_FAILURE);
+}
+
+static void allow(char *shell, mode_t mask)
+{
+ struct su_initiator *from = &su_from;
+ struct su_request *to = &su_to;
+ char *exe = NULL;
+
+ umask(mask);
+ send_intent(&su_from, &su_to, "", 1, 1);
+
+ if (!strcmp(shell, "")) {
+ strcpy(shell , "/system/bin/sh");
+ }
+ exe = strrchr (shell, '/') + 1;
+ setresgid(to->uid, to->uid, to->uid);
+ setresuid(to->uid, to->uid, to->uid);
+ LOGD("%u %s executing %u %s using shell %s : %s", from->uid, from->bin,
+ to->uid, to->command, shell, exe);
+ if (strcmp(to->command, DEFAULT_COMMAND)) {
+ execl(shell, exe, "-c", to->command, (char*)NULL);
+ } else {
+ execl(shell, exe, "-", (char*)NULL);
+ }
+ PLOGE("exec");
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char *argv[])
+{
+ struct stat st;
+ static int socket_serv_fd = -1;
+ char buf[64], shell[PATH_MAX], *result;
+ int i, dballow;
+ mode_t orig_umask;
+
+ for (i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--command")) {
+ if (++i < argc) {
+ su_to.command = argv[i];
+ } else {
+ usage();
+ }
+ } else if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "--shell")) {
+ if (++i < argc) {
+ strncpy(shell, argv[i], sizeof(shell));
+ shell[sizeof(shell) - 1] = 0;
+ } else {
+ usage();
+ }
+ } else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--version")) {
+ printf("%s\n", VERSION);
+ exit(EXIT_SUCCESS);
+ } else if (!strcmp(argv[i], "-V")) {
+ printf("%d\n", VERSION_CODE);
+ exit(EXIT_SUCCESS);
+ } else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
+ usage();
+ } else if (!strcmp(argv[i], "-") || !strcmp(argv[i], "-l") ||
+ !strcmp(argv[i], "--login")) {
+ ++i;
+ break;
+ } else {
+ break;
}
- } else if (argc > 3) {
- /* Copy the rest of the args from main. */
- char *exec_args[argc - 1];
- memset(exec_args, 0, sizeof(exec_args));
- memcpy(exec_args, &argv[2], sizeof(exec_args));
- if (execvp(argv[2], exec_args) < 0) {
- fprintf(stderr, "su: exec failed for %s Error:%s\n", argv[2],
- strerror(errno));
- return -errno;
+ }
+ if (i < argc-1) {
+ usage();
+ }
+ if (i == argc-1) {
+ struct passwd *pw;
+ pw = getpwnam(argv[i]);
+ if (!pw) {
+ su_to.uid = atoi(argv[i]);
+ } else {
+ su_to.uid = pw->pw_uid;
}
}
- /* Default exec shell. */
- execlp("/system/bin/sh", "sh", NULL);
+ if (from_init(&su_from) < 0) {
+ deny();
+ }
+
+ orig_umask = umask(027);
+
+ if (su_from.uid == AID_ROOT || su_from.uid == AID_SHELL)
+ allow(shell, orig_umask);
+
+ if (stat(REQUESTOR_DATA_PATH, &st) < 0) {
+ PLOGE("stat");
+ deny();
+ }
+
+ if (st.st_gid != st.st_uid)
+ {
+ LOGE("Bad uid/gid %d/%d for Superuser Requestor application",
+ (int)st.st_uid, (int)st.st_gid);
+ deny();
+ }
+
+ if (mkdir(REQUESTOR_CACHE_PATH, 0770) >= 0) {
+ chown(REQUESTOR_CACHE_PATH, st.st_uid, st.st_gid);
+ }
+
+ setgroups(0, NULL);
+ setegid(st.st_gid);
+ seteuid(st.st_uid);
+
+ LOGV("sudb - Opening database");
+ db = database_init();
+ if (!db) {
+ LOGE("sudb - Could not open database, prompt user");
+ // if the database could not be opened, we can assume we need to
+ // prompt the user
+ dballow = DB_INTERACTIVE;
+ } else {
+ //LOGD("sudb - Database opened");
+ dballow = database_check(db, &su_from, &su_to);
+ // Close the database, we're done with it. If it stays open,
+ // it will cause problems
+ sqlite3_close(db);
+ db = NULL;
+ LOGV("sudb - Database closed");
+ }
+
+ switch (dballow) {
+ case DB_DENY: deny();
+ case DB_ALLOW: allow(shell, orig_umask);
+ case DB_INTERACTIVE: break;
+ default: deny();
+ }
+
+ socket_serv_fd = socket_create_temp();
+ if (socket_serv_fd < 0) {
+ deny();
+ }
+
+ signal(SIGHUP, cleanup_signal);
+ signal(SIGPIPE, cleanup_signal);
+ signal(SIGTERM, cleanup_signal);
+ signal(SIGABRT, cleanup_signal);
+ atexit(cleanup);
+
+ if (send_intent(&su_from, &su_to, socket_path, -1, 0) < 0) {
+ deny();
+ }
+
+ if (socket_receive_result(socket_serv_fd, buf, sizeof(buf)) < 0) {
+ deny();
+ }
+
+ close(socket_serv_fd);
+ socket_cleanup();
+
+ result = buf;
+
+ if (!strcmp(result, "DENY")) {
+ deny();
+ } else if (!strcmp(result, "ALLOW")) {
+ allow(shell, orig_umask);
+ } else {
+ LOGE("unknown response from Superuser Requestor: %s", result);
+ deny();
+ }
- fprintf(stderr, "su: exec failed\n");
- return 1;
+ deny();
+ return -1;
}