--- /dev/null
+bin
+libs
+obj
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="src" path="gen"/>
+ <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
+ <classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
+ <classpathentry kind="output" path="bin/classes"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>Superuser</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.android.ide.eclipse.adt.ApkBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>com.android.ide.eclipse.adt.AndroidNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.koushikdutta.superuser"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk
+ android:minSdkVersion="8"
+ android:targetSdkVersion="17" />
+
+ <application
+ android:allowBackup="true"
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme" >
+ <activity
+ android:name="com.koushikdutta.superuser.MainActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <receiver android:name=".SuReceiver">
+ <intent-filter>
+ <action android:name="com.koushikdutta.superuser.REQUEST" />
+ </intent-filter>
+ </receiver>
+ </application>
+
+</manifest>
\ No newline at end of file
--- /dev/null
+/** Automatically generated file. DO NOT MODIFY */
+package com.koushikdutta.superuser;
+
+public final class BuildConfig {
+ public final static boolean DEBUG = true;
+}
\ No newline at end of file
--- /dev/null
+/* AUTO-GENERATED FILE. DO NOT MODIFY.
+ *
+ * This class was automatically generated by the
+ * aapt tool from the resource data it found. It
+ * should not be modified by hand.
+ */
+
+package com.koushikdutta.superuser;
+
+public final class R {
+ public static final class attr {
+ }
+ public static final class dimen {
+ /** Default screen margins, per the Android Design guidelines.
+
+ Customize dimensions originally defined in res/values/dimens.xml (such as
+ screen margins) for sw720dp devices (e.g. 10" tablets) in landscape here.
+
+ */
+ public static final int activity_horizontal_margin=0x7f040000;
+ public static final int activity_vertical_margin=0x7f040001;
+ }
+ public static final class drawable {
+ public static final int ic_launcher=0x7f020000;
+ }
+ public static final class id {
+ public static final int action_settings=0x7f080000;
+ }
+ public static final class layout {
+ public static final int activity_main=0x7f030000;
+ }
+ public static final class menu {
+ public static final int main=0x7f070000;
+ }
+ public static final class string {
+ public static final int action_settings=0x7f050001;
+ public static final int app_name=0x7f050000;
+ public static final int hello_world=0x7f050002;
+ }
+ public static final class style {
+ /**
+ Base application theme, dependent on API level. This theme is replaced
+ by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+
+
+ Theme customizations available in newer API levels can go in
+ res/values-vXX/styles.xml, while customizations related to
+ backward-compatibility can go here.
+
+
+ Base application theme for API 11+. This theme completely replaces
+ AppBaseTheme from res/values/styles.xml on API 11+ devices.
+
+ API 11 theme customizations can go here.
+
+ Base application theme for API 14+. This theme completely replaces
+ AppBaseTheme from BOTH res/values/styles.xml and
+ res/values-v11/styles.xml on API 14+ devices.
+
+ API 14 theme customizations can go here.
+ */
+ public static final int AppBaseTheme=0x7f060000;
+ /** Application theme.
+ All customizations that are NOT specific to a particular API-level can go here.
+ */
+ public static final int AppTheme=0x7f060001;
+ }
+}
--- /dev/null
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_ARM_MODE := arm
+LOCAL_MODULE := su
+LOCAL_LDFLAGS := -static
+LOCAL_STATIC_LIBRARIES := log
+LOCAL_SRC_FILES := su/su.c su/activity.c su/db.c su/utils.c
+include $(BUILD_EXECUTABLE)
--- /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 <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#include "su.h"
+
+static void kill_child(pid_t pid)
+{
+ LOGD("killing child %d", pid);
+ if (pid) {
+ sigset_t set, old;
+
+ sigemptyset(&set);
+ sigaddset(&set, SIGCHLD);
+ if (sigprocmask(SIG_BLOCK, &set, &old)) {
+ PLOGE("sigprocmask(SIG_BLOCK)");
+ return;
+ }
+ if (kill(pid, SIGKILL))
+ PLOGE("kill (%d)", pid);
+ else if (sigsuspend(&old) && errno != EINTR)
+ PLOGE("sigsuspend");
+ if (sigprocmask(SIG_SETMASK, &old, NULL))
+ PLOGE("sigprocmask(SIG_BLOCK)");
+ }
+}
+
+int send_intent(struct su_context *ctx, allow_t allow, const char *action)
+{
+ const char *socket_path;
+ unsigned int uid = ctx->from.uid;
+ __sighandler_t handler;
+ pid_t pid;
+
+ if (allow == INTERACTIVE) {
+ socket_path = ctx->sock_path;
+ handler = sigchld_handler;
+ } else {
+ socket_path = "";
+ handler = SIG_IGN;
+ }
+
+ pid = fork();
+ /* Child */
+ if (!pid) {
+ char command[ARG_MAX];
+
+ snprintf(command, sizeof(command),
+ "exec /system/bin/am broadcast --user %d -a %s --es socket '%s' "
+ "--ei caller_uid %d --ei allow %d --es desired_cmd '%s' "
+ "--ei all %d --ei version_code %d > /dev/null 2> /dev/null",
+ ctx->user.userid, action, socket_path, uid, allow, get_command(&ctx->to),
+ ctx->to.all, VERSION_CODE);
+
+ char *args[] = { "sh", "-c", command, NULL, };
+
+ /*
+ * before sending the intent, make sure the effective uid/gid match
+ * the real uid/gid, otherwise LD_LIBRARY_PATH is wiped
+ * in Android 4.0+.
+ */
+ set_identity(uid);
+ int zero = open("/dev/zero", O_RDONLY | O_CLOEXEC);
+ dup2(zero, 0);
+ int null = open("/dev/null", O_WRONLY | O_CLOEXEC);
+ dup2(null, 1);
+ dup2(null, 2);
+ LOGD("Executing %s\n", command);
+ execv(_PATH_BSHELL, args);
+ PLOGE("exec am");
+ _exit(EXIT_FAILURE);
+ }
+
+ /* Parent */
+ if (pid < 0) {
+ PLOGE("fork");
+ return -1;
+ }
+ if (allow != INTERACTIVE) {
+ waitpid(pid, NULL, 0);
+ signal(SIGCHLD, SIG_DFL);
+ }
+ return 0;
+}
--- /dev/null
+/*
+** Copyright 2010, Adam Shanks (@ChainsDD)
+**
+** 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 <stdlib.h>
+#include <stdio.h>
+#include <limits.h>
+
+#include "su.h"
+
+int database_check(struct su_context *ctx)
+{
+ FILE *fp;
+ char filename[PATH_MAX];
+ char allow[7];
+ int last = 0;
+ int from_uid = ctx->from.uid;
+
+ if (ctx->user.owner_mode) {
+ from_uid = from_uid % 100000;
+ }
+
+ snprintf(filename, sizeof(filename),
+ "%s/%u-%u", ctx->user.store_path, from_uid, ctx->to.uid);
+ if ((fp = fopen(filename, "r"))) {
+ LOGD("Found file %s", filename);
+
+ while (fgets(allow, sizeof(allow), fp)) {
+ last = strlen(allow) - 1;
+ if (last >= 0)
+ allow[last] = 0;
+
+ char cmd[ARG_MAX];
+ fgets(cmd, sizeof(cmd), fp);
+ /* skip trailing '\n' */
+ last = strlen(cmd) - 1;
+ if (last >= 0)
+ cmd[last] = 0;
+
+ LOGD("Comparing '%s' to '%s'", cmd, get_command(&ctx->to));
+ if (strcmp(cmd, get_command(&ctx->to)) == 0)
+ break;
+ else if (strcmp(cmd, "any") == 0) {
+ ctx->to.all = 1;
+ break;
+ }
+ else
+ strcpy(allow, "prompt");
+ }
+ fclose(fp);
+ } else if ((fp = fopen(ctx->user.store_default, "r"))) {
+ LOGD("Using default file %s", ctx->user.store_default);
+ fgets(allow, sizeof(allow), fp);
+ last = strlen(allow) - 1;
+ if (last >=0)
+ allow[last] = 0;
+
+ fclose(fp);
+ }
+
+ if (strcmp(allow, "allow") == 0) {
+ return ALLOW;
+ } else if (strcmp(allow, "deny") == 0) {
+ return DENY;
+ } else {
+ if (ctx->user.userid != 0 && ctx->user.owner_mode) {
+ return DENY;
+ } else {
+ return INTERACTIVE;
+ }
+ }
+}
--- /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 <sys/types.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 <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <stdint.h>
+#include <pwd.h>
+#include <sys/stat.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <pwd.h>
+
+#include "su.h"
+#include "utils.h"
+
+int get_shell_uid() {
+ struct passwd* ppwd = getpwnam("shell");
+ if (NULL == ppwd) {
+ return -1;
+ }
+
+ return ppwd->pw_uid;
+}
+
+void exec_log(char *priority, char* logline) {
+ int pid;
+ if ((pid = fork()) == 0) {
+ execl("/system/bin/log", "/system/bin/log", "-p", priority, "-t", LOG_TAG, logline);
+ exit(0);
+ }
+}
+
+void exec_loge(const char* fmt, ...) {
+ va_list args;
+
+ char logline[PATH_MAX];
+ va_start(args, fmt);
+ vsnprintf(logline, PATH_MAX, fmt, args);
+ va_end(args);
+ exec_log("e", logline);
+}
+void exec_logw(const char* fmt, ...) {
+ va_list args;
+
+ char logline[PATH_MAX];
+ va_start(args, fmt);
+ vsnprintf(logline, PATH_MAX, fmt, args);
+ va_end(args);
+ exec_log("w", logline);
+}
+void exec_logd(const char* fmt, ...) {
+ va_list args;
+
+ char logline[PATH_MAX];
+ va_start(args, fmt);
+ vsnprintf(logline, PATH_MAX, fmt, args);
+ va_end(args);
+ exec_log("d", logline);
+}
+
+static int from_init(struct su_initiator *from)
+{
+ 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;
+ }
+
+ 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 {
+ from->args[0] = '\0';
+ }
+
+ /* 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 read_options(struct su_context *ctx)
+{
+ char mode[12];
+ FILE *fp;
+ if ((fp = fopen(REQUESTOR_OPTIONS, "r"))) {
+ fgets(mode, sizeof(mode), fp);
+ if (strcmp(mode, "user\n") == 0) {
+ ctx->user.owner_mode = 0;
+ } else if (strcmp(mode, "owner\n") == 0) {
+ ctx->user.owner_mode = 1;
+ }
+ }
+}
+
+static void user_init(struct su_context *ctx)
+{
+ if (ctx->from.uid > 99999) {
+ ctx->user.userid = ctx->from.uid / 100000;
+ if (!ctx->user.owner_mode) {
+ snprintf(ctx->user.data_path, PATH_MAX, "/data/user/%d/%s",
+ ctx->user.userid, REQUESTOR);
+ snprintf(ctx->user.store_path, PATH_MAX, "/data/user/%d/%s/files/stored",
+ ctx->user.userid, REQUESTOR);
+ snprintf(ctx->user.store_default, PATH_MAX, "/data/user/%d/%s/files/stored/default",
+ ctx->user.userid, REQUESTOR);
+ }
+ }
+}
+
+static void populate_environment(const struct su_context *ctx)
+{
+ struct passwd *pw;
+
+ if (ctx->to.keepenv)
+ return;
+
+ pw = getpwuid(ctx->to.uid);
+ if (pw) {
+ setenv("HOME", pw->pw_dir, 1);
+ setenv("SHELL", ctx->to.shell, 1);
+ if (ctx->to.login || ctx->to.uid) {
+ setenv("USER", pw->pw_name, 1);
+ setenv("LOGNAME", pw->pw_name, 1);
+ }
+ }
+}
+
+void set_identity(unsigned int uid)
+{
+ /*
+ * Set effective uid back to root, otherwise setres[ug]id will fail
+ * if uid isn't root.
+ */
+ if (seteuid(0)) {
+ PLOGE("seteuid (root)");
+ exit(EXIT_FAILURE);
+ }
+ if (setresgid(uid, uid, uid)) {
+ PLOGE("setresgid (%u)", uid);
+ exit(EXIT_FAILURE);
+ }
+ if (setresuid(uid, uid, uid)) {
+ PLOGE("setresuid (%u)", uid);
+ exit(EXIT_FAILURE);
+ }
+}
+
+/*
+ * For use in signal handlers/atexit-function
+ * NOTE: su_ctx points to main's local variable.
+ * It's OK due to the program uses exit(3), not return from main()
+ */
+static struct su_context *su_ctx = NULL;
+
+static int socket_create_temp(char *path, size_t len)
+{
+ int fd;
+ struct sockaddr_un sun;
+
+ fd = socket(AF_LOCAL, SOCK_STREAM, 0);
+ if (fd < 0) {
+ PLOGE("socket");
+ return -1;
+ }
+ if (fcntl(fd, F_SETFD, FD_CLOEXEC)) {
+ PLOGE("fcntl FD_CLOEXEC");
+ goto err;
+ }
+
+ memset(&sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ snprintf(path, len, "%s/.socket%d", REQUESTOR_CACHE_PATH, getpid());
+ memset(sun.sun_path, 0, sizeof(sun.sun_path));
+ snprintf(sun.sun_path + 1, sizeof(sun.sun_path) - 1, "%s", path);
+
+ /*
+ * Delete the socket to protect from situations when
+ * something bad occured previously and the kernel reused pid from that process.
+ * Small probability, isn't it.
+ */
+ // unlink(sun.sun_path);
+
+ if (bind(fd, (struct sockaddr*)&sun, sizeof(sun)) < 0) {
+ PLOGE("bind");
+ goto err;
+ }
+
+ if (listen(fd, 1) < 0) {
+ PLOGE("listen");
+ goto err;
+ }
+
+ return fd;
+err:
+ close(fd);
+ return -1;
+}
+
+static int socket_accept(int serv_fd)
+{
+ struct timeval tv;
+ fd_set fds;
+ int fd, rc;
+
+ /* 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);
+ do {
+ rc = select(serv_fd + 1, &fds, NULL, NULL, &tv);
+ } while (rc < 0 && errno == EINTR);
+ if (rc < 1) {
+ PLOGE("select");
+ return -1;
+ }
+
+ fd = accept(serv_fd, NULL, NULL);
+ if (fd < 0) {
+ PLOGE("accept");
+ return -1;
+ }
+
+ return fd;
+}
+
+static int socket_send_request(int fd, const struct su_context *ctx)
+{
+ size_t len;
+ size_t bin_size, cmd_size;
+ char *cmd;
+
+#define write_token(fd, data) \
+do { \
+ uint32_t __data = htonl(data); \
+ size_t __count = sizeof(__data); \
+ size_t __len = write((fd), &__data, __count); \
+ if (__len != __count) { \
+ PLOGE("write(" #data ")"); \
+ return -1; \
+ } \
+} while (0)
+
+ write_token(fd, PROTO_VERSION);
+ write_token(fd, PATH_MAX);
+ write_token(fd, ARG_MAX);
+ write_token(fd, ctx->from.uid);
+ write_token(fd, ctx->to.uid);
+ bin_size = strlen(ctx->from.bin) + 1;
+ write_token(fd, bin_size);
+ len = write(fd, ctx->from.bin, bin_size);
+ if (len != bin_size) {
+ PLOGE("write(bin)");
+ return -1;
+ }
+ cmd = get_command(&ctx->to);
+ cmd_size = strlen(cmd) + 1;
+ write_token(fd, cmd_size);
+ len = write(fd, cmd, cmd_size);
+ if (len != cmd_size) {
+ PLOGE("write(cmd)");
+ return -1;
+ }
+ return 0;
+}
+
+static int socket_receive_result(int fd, char *result, ssize_t result_len)
+{
+ ssize_t len;
+
+ len = read(fd, result, result_len-1);
+ if (len < 0) {
+ PLOGE("read(result)");
+ return -1;
+ }
+ result[len] = '\0';
+
+ return 0;
+}
+
+static void usage(int status)
+{
+ FILE *stream = (status == EXIT_SUCCESS) ? stdout : stderr;
+
+ fprintf(stream,
+ "Usage: su [options] [--] [-] [LOGIN] [--] [args...]\n\n"
+ "Options:\n"
+ " -c, --command COMMAND pass COMMAND to the invoked shell\n"
+ " -h, --help display this help message and exit\n"
+ " -, -l, --login pretend the shell to be a login shell\n"
+ " -m, -p,\n"
+ " --preserve-environment do not change environment variables\n"
+ " -s, --shell SHELL use SHELL instead of the default " DEFAULT_SHELL "\n"
+ " -v, --version display version number and exit\n"
+ " -V display version code and exit,\n"
+ " this is used almost exclusively by Superuser.apk\n");
+ exit(status);
+}
+
+static __attribute__ ((noreturn)) void deny(struct su_context *ctx)
+{
+ char *cmd = get_command(&ctx->to);
+
+ // No send to UI denied requests for shell and root users (they are in the log)
+ if( ctx->from.uid != AID_SHELL && ctx->from.uid != AID_ROOT ) {
+ send_intent(ctx, DENY, ACTION_RESULT);
+ }
+ LOGW("request rejected (%u->%u %s)", ctx->from.uid, ctx->to.uid, cmd);
+ fprintf(stderr, "%s\n", strerror(EACCES));
+ exit(EXIT_FAILURE);
+}
+
+static __attribute__ ((noreturn)) void allow(struct su_context *ctx)
+{
+ char *arg0;
+ int argc, err;
+
+ umask(ctx->umask);
+ // No send to UI accepted requests for shell and root users (they are in the log)
+ if( ctx->from.uid != AID_SHELL && ctx->from.uid != AID_ROOT ) {
+ send_intent(ctx, ALLOW, ACTION_RESULT);
+ }
+
+ arg0 = strrchr (ctx->to.shell, '/');
+ arg0 = (arg0) ? arg0 + 1 : ctx->to.shell;
+ if (ctx->to.login) {
+ int s = strlen(arg0) + 2;
+ char *p = malloc(s);
+
+ if (!p)
+ exit(EXIT_FAILURE);
+
+ *p = '-';
+ strcpy(p + 1, arg0);
+ arg0 = p;
+ }
+
+ populate_environment(ctx);
+ set_identity(ctx->to.uid);
+
+#define PARG(arg) \
+ (ctx->to.optind + (arg) < ctx->to.argc) ? " " : "", \
+ (ctx->to.optind + (arg) < ctx->to.argc) ? ctx->to.argv[ctx->to.optind + (arg)] : ""
+
+ LOGD("%u %s executing %u %s using shell %s : %s%s%s%s%s%s%s%s%s%s%s%s%s%s",
+ ctx->from.uid, ctx->from.bin,
+ ctx->to.uid, get_command(&ctx->to), ctx->to.shell,
+ arg0, PARG(0), PARG(1), PARG(2), PARG(3), PARG(4), PARG(5),
+ (ctx->to.optind + 6 < ctx->to.argc) ? " ..." : "");
+
+ argc = ctx->to.optind;
+ if (ctx->to.command) {
+ ctx->to.argv[--argc] = ctx->to.command;
+ ctx->to.argv[--argc] = "-c";
+ }
+ ctx->to.argv[--argc] = arg0;
+ execv(ctx->to.shell, ctx->to.argv + argc);
+ err = errno;
+ PLOGE("exec");
+ fprintf(stderr, "Cannot execute %s: %s\n", ctx->to.shell, strerror(err));
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * CyanogenMod-specific behavior
+ *
+ * we can't simply use the property service, since we aren't launched from init
+ * and can't trust the location of the property workspace.
+ * Find the properties ourselves.
+ */
+int access_disabled(const struct su_initiator *from)
+{
+ char *data;
+ char build_type[PROPERTY_VALUE_MAX];
+ char debuggable[PROPERTY_VALUE_MAX], enabled[PROPERTY_VALUE_MAX];
+ size_t len;
+
+ data = read_file("/system/build.prop");
+ if (check_property(data, "ro.cm.version")) {
+ get_property(data, build_type, "ro.build.type", "");
+ free(data);
+
+ data = read_file("/default.prop");
+ get_property(data, debuggable, "ro.debuggable", "0");
+ free(data);
+ /* only allow su on debuggable builds */
+ if (strcmp("1", debuggable) != 0) {
+ LOGE("Root access is disabled on non-debug builds");
+ return 1;
+ }
+
+ data = read_file("/data/property/persist.sys.root_access");
+ if (data != NULL) {
+ len = strlen(data);
+ if (len >= PROPERTY_VALUE_MAX)
+ memcpy(enabled, "1", 2);
+ else
+ memcpy(enabled, data, len + 1);
+ free(data);
+ } else
+ memcpy(enabled, "1", 2);
+
+ /* enforce persist.sys.root_access on non-eng builds for apps */
+ if (strcmp("eng", build_type) != 0 &&
+ from->uid != AID_SHELL && from->uid != AID_ROOT &&
+ (atoi(enabled) & CM_ROOT_ACCESS_APPS_ONLY) != CM_ROOT_ACCESS_APPS_ONLY ) {
+ LOGE("Apps root access is disabled by system setting - "
+ "enable it under settings -> developer options");
+ return 1;
+ }
+
+ /* disallow su in a shell if appropriate */
+ if (from->uid == AID_SHELL &&
+ (atoi(enabled) & CM_ROOT_ACCESS_ADB_ONLY) != CM_ROOT_ACCESS_ADB_ONLY ) {
+ LOGE("Shell root access is disabled by a system setting - "
+ "enable it under settings -> developer options");
+ return 1;
+ }
+
+ }
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ LOGD("su invoked.");
+
+ struct su_context ctx = {
+ .from = {
+ .pid = -1,
+ .uid = 0,
+ .bin = "",
+ .args = "",
+ },
+ .to = {
+ .uid = AID_ROOT,
+ .login = 0,
+ .keepenv = 0,
+ .shell = DEFAULT_SHELL,
+ .command = NULL,
+ .argv = argv,
+ .argc = argc,
+ .optind = 0,
+ },
+ .user = {
+ .userid = 0,
+ .owner_mode = -1,
+ .data_path = REQUESTOR_DATA_PATH,
+ .store_path = REQUESTOR_STORED_PATH,
+ .store_default = REQUESTOR_STORED_DEFAULT,
+ },
+ };
+ struct stat st;
+ int c, socket_serv_fd, fd;
+ char buf[64], *result;
+ allow_t dballow;
+ struct option long_opts[] = {
+ { "command", required_argument, NULL, 'c' },
+ { "help", no_argument, NULL, 'h' },
+ { "login", no_argument, NULL, 'l' },
+ { "preserve-environment", no_argument, NULL, 'p' },
+ { "shell", required_argument, NULL, 's' },
+ { "version", no_argument, NULL, 'v' },
+ { NULL, 0, NULL, 0 },
+ };
+
+ while ((c = getopt_long(argc, argv, "+c:hlmps:Vv", long_opts, NULL)) != -1) {
+ switch(c) {
+ case 'c':
+ ctx.to.command = optarg;
+ break;
+ case 'h':
+ usage(EXIT_SUCCESS);
+ break;
+ case 'l':
+ ctx.to.login = 1;
+ break;
+ case 'm':
+ case 'p':
+ ctx.to.keepenv = 1;
+ break;
+ case 's':
+ ctx.to.shell = optarg;
+ break;
+ case 'V':
+ printf("%d\n", VERSION_CODE);
+ exit(EXIT_SUCCESS);
+ case 'v':
+ printf("%s\n", VERSION);
+ exit(EXIT_SUCCESS);
+ default:
+ /* Bionic getopt_long doesn't terminate its error output by newline */
+ fprintf(stderr, "\n");
+ usage(2);
+ }
+ }
+ if (optind < argc && !strcmp(argv[optind], "-")) {
+ ctx.to.login = 1;
+ optind++;
+ }
+ /* username or uid */
+ if (optind < argc && strcmp(argv[optind], "--")) {
+ struct passwd *pw;
+ pw = getpwnam(argv[optind]);
+ if (!pw) {
+ char *endptr;
+
+ /* It seems we shouldn't do this at all */
+ errno = 0;
+ ctx.to.uid = strtoul(argv[optind], &endptr, 10);
+ if (errno || *endptr) {
+ LOGE("Unknown id: %s\n", argv[optind]);
+ fprintf(stderr, "Unknown id: %s\n", argv[optind]);
+ exit(EXIT_FAILURE);
+ }
+ } else {
+ ctx.to.uid = pw->pw_uid;
+ }
+ optind++;
+ }
+ if (optind < argc && !strcmp(argv[optind], "--")) {
+ optind++;
+ }
+ ctx.to.optind = optind;
+
+ su_ctx = &ctx;
+ if (from_init(&ctx.from) < 0) {
+ deny(&ctx);
+ }
+
+ read_options(&ctx);
+ user_init(&ctx);
+
+ if (ctx.user.owner_mode == -1 && ctx.user.userid != 0)
+ deny(&ctx);
+
+ if (access_disabled(&ctx.from))
+ deny(&ctx);
+
+ ctx.umask = umask(027);
+
+ /*
+ * set LD_LIBRARY_PATH if the linker has wiped out it due to we're suid.
+ * This occurs on Android 4.0+
+ */
+ setenv("LD_LIBRARY_PATH", "/vendor/lib:/system/lib", 0);
+ if (ctx.from.uid == AID_ROOT || ctx.from.uid == AID_SHELL)
+ allow(&ctx);
+
+ if (stat(ctx.user.data_path, &st) < 0) {
+ PLOGE("stat");
+ deny(&ctx);
+ }
+
+ 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(&ctx);
+ }
+
+ mkdir(REQUESTOR_CACHE_PATH, 0770);
+ if (chown(REQUESTOR_CACHE_PATH, st.st_uid, st.st_gid)) {
+ PLOGE("chown (%s, %ld, %ld)", REQUESTOR_CACHE_PATH, st.st_uid, st.st_gid);
+ deny(&ctx);
+ }
+
+ if (setgroups(0, NULL)) {
+ PLOGE("setgroups");
+ deny(&ctx);
+ }
+ if (setegid(st.st_gid)) {
+ PLOGE("setegid (%lu)", st.st_gid);
+ deny(&ctx);
+ }
+ if (seteuid(st.st_uid)) {
+ PLOGE("seteuid (%lu)", st.st_uid);
+ deny(&ctx);
+ }
+
+ dballow = database_check(&ctx);
+ switch (dballow) {
+ case INTERACTIVE: break;
+ case ALLOW: allow(&ctx); /* never returns */
+ case DENY:
+ default: deny(&ctx); /* never returns too */
+ }
+
+ socket_serv_fd = socket_create_temp(ctx.sock_path, sizeof(ctx.sock_path));
+ LOGD(ctx.sock_path);
+ if (socket_serv_fd < 0) {
+ deny(&ctx);
+ }
+
+ if (send_intent(&ctx, INTERACTIVE, ACTION_REQUEST) < 0) {
+ deny(&ctx);
+ }
+
+ fd = socket_accept(socket_serv_fd);
+ LOGE("accepted");
+ if (fd < 0) {
+ deny(&ctx);
+ }
+ if (socket_send_request(fd, &ctx)) {
+ deny(&ctx);
+ }
+ if (socket_receive_result(fd, buf, sizeof(buf))) {
+ deny(&ctx);
+ }
+
+ close(fd);
+ close(socket_serv_fd);
+
+ result = buf;
+
+#define SOCKET_RESPONSE "socket:"
+ if (strncmp(result, SOCKET_RESPONSE, sizeof(SOCKET_RESPONSE) - 1))
+ LOGW("SECURITY RISK: Requestor still receives credentials in intent");
+ else
+ result += sizeof(SOCKET_RESPONSE) - 1;
+
+ if (!strcmp(result, "DENY")) {
+ deny(&ctx);
+ } else if (!strcmp(result, "ALLOW")) {
+ allow(&ctx);
+ } else {
+ LOGE("unknown response from Superuser Requestor: %s", result);
+ deny(&ctx);
+ }
+}
--- /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.
+*/
+
+#ifndef SU_h
+#define SU_h 1
+
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
+#define LOG_TAG "su"
+
+#ifndef AID_SHELL
+#define AID_SHELL (get_shell_uid())
+#endif
+
+#ifndef AID_ROOT
+#define AID_ROOT 0
+#endif
+
+// CyanogenMod-specific behavior
+#define CM_ROOT_ACCESS_DISABLED 0
+#define CM_ROOT_ACCESS_APPS_ONLY 1
+#define CM_ROOT_ACCESS_ADB_ONLY 2
+#define CM_ROOT_ACCESS_APPS_AND_ADB 3
+
+#define REQUESTOR "com.koushikdutta.superuser"
+#define REQUESTOR_DATA_PATH "/data/data/" REQUESTOR
+#define REQUESTOR_CACHE_PATH "/dev/" REQUESTOR
+
+#define REQUESTOR_STORED_PATH REQUESTOR_DATA_PATH "/files/stored"
+#define REQUESTOR_STORED_DEFAULT REQUESTOR_STORED_PATH "/default"
+#define REQUESTOR_OPTIONS REQUESTOR_STORED_PATH "/options"
+
+/* intent actions */
+#define ACTION_REQUEST REQUESTOR ".REQUEST"
+#define ACTION_RESULT REQUESTOR ".RESULT"
+
+#define DEFAULT_SHELL "/system/bin/sh"
+
+#ifdef SU_LEGACY_BUILD
+#define VERSION_EXTRA "l"
+#else
+#define VERSION_EXTRA ""
+#endif
+
+#define VERSION "3.3" VERSION_EXTRA
+#define VERSION_CODE 19
+
+#define DATABASE_VERSION 6
+#define PROTO_VERSION 0
+
+struct su_initiator {
+ pid_t pid;
+ unsigned uid;
+ unsigned user;
+ char bin[PATH_MAX];
+ char args[4096];
+};
+
+struct su_request {
+ unsigned uid;
+ int login;
+ int keepenv;
+ char *shell;
+ char *command;
+ char **argv;
+ int argc;
+ int optind;
+ int appId;
+ int all;
+};
+
+struct su_user_info {
+ unsigned userid;
+ int owner_mode;
+ char data_path[PATH_MAX];
+ char store_path[PATH_MAX];
+ char store_default[PATH_MAX];
+};
+
+struct su_context {
+ struct su_initiator from;
+ struct su_request to;
+ struct su_user_info user;
+ mode_t umask;
+ char sock_path[PATH_MAX];
+};
+
+typedef enum {
+ INTERACTIVE = -1,
+ DENY = 0,
+ ALLOW = 1,
+} allow_t;
+
+extern allow_t database_check(struct su_context *ctx);
+extern void set_identity(unsigned int uid);
+extern int send_intent(struct su_context *ctx,
+ allow_t allow, const char *action);
+extern void sigchld_handler(int sig);
+
+static inline char *get_command(const struct su_request *to)
+{
+ return (to->command) ? to->command : to->shell;
+}
+
+void exec_loge(const char* fmt, ...);
+void exec_logw(const char* fmt, ...);
+void exec_logd(const char* fmt, ...);
+#define LOGE exec_loge
+#define LOGD exec_logd
+#define LOGW exec_logw
+
+#if 0
+#undef LOGE
+#define LOGE(fmt,args...) fprintf(stderr, fmt, ##args)
+#undef LOGD
+#define LOGD(fmt,args...) fprintf(stderr, fmt, ##args)
+#undef LOGW
+#define LOGW(fmt,args...) fprintf(stderr, fmt, ##args)
+#endif
+
+#include <errno.h>
+#include <string.h>
+#define PLOGE(fmt,args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno))
+#define PLOGEV(fmt,err,args...) LOGE(fmt " failed with %d: %s", ##args, err, strerror(err))
+
+#endif
--- /dev/null
+/*
+** Copyright 2012, The CyanogenMod 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
+**
+** 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 <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "utils.h"
+
+/* reads a file, making sure it is terminated with \n \0 */
+char* read_file(const char *fn)
+{
+ struct stat st;
+ char *data = NULL;
+
+ int fd = open(fn, O_RDONLY);
+ if (fd < 0) return data;
+
+ if (fstat(fd, &st)) goto oops;
+
+ data = malloc(st.st_size + 2);
+ if (!data) goto oops;
+
+ if (read(fd, data, st.st_size) != st.st_size) goto oops;
+ close(fd);
+ data[st.st_size] = '\n';
+ data[st.st_size + 1] = 0;
+ return data;
+
+oops:
+ close(fd);
+ if (data) free(data);
+ return NULL;
+}
+
+int get_property(const char *data, char *found, const char *searchkey, const char *not_found)
+{
+ char *key, *value, *eol, *sol, *tmp;
+ if (data == NULL) goto defval;
+ int matched = 0;
+ sol = strdup(data);
+ while((eol = strchr(sol, '\n'))) {
+ key = sol;
+ *eol++ = 0;
+ sol = eol;
+
+ value = strchr(key, '=');
+ if(value == 0) continue;
+ *value++ = 0;
+
+ while(isspace(*key)) key++;
+ if(*key == '#') continue;
+ tmp = value - 2;
+ while((tmp > key) && isspace(*tmp)) *tmp-- = 0;
+
+ while(isspace(*value)) value++;
+ tmp = eol - 2;
+ while((tmp > value) && isspace(*tmp)) *tmp-- = 0;
+
+ if (strncmp(searchkey, key, strlen(searchkey)) == 0) {
+ matched = 1;
+ break;
+ }
+ }
+ int len;
+ if (matched) {
+ len = strlen(value);
+ if (len >= PROPERTY_VALUE_MAX)
+ return -1;
+ memcpy(found, value, len + 1);
+ } else goto defval;
+ return len;
+
+defval:
+ len = strlen(not_found);
+ memcpy(found, not_found, len + 1);
+ return len;
+}
+
+/*
+ * Fast version of get_property which purpose is to check
+ * whether the property with given prefix exists.
+ *
+ * Assume nobody is stupid enough to put a propery with prefix ro.cm.version
+ * in his build.prop on a non-CM ROM and comment it out.
+ */
+int check_property(const char *data, const char *prefix)
+{
+ if (!data)
+ return 0;
+ return strstr(data, prefix) != NULL;
+}
--- /dev/null
+/*
+** Copyright 2012, The CyanogenMod 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
+**
+** 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.
+*/
+
+#ifndef _UTILS_H_
+#define _UTILS_H_
+
+#ifndef PROPERTY_VALUE_MAX
+#define PROPERTY_VALUE_MAX 92
+#endif
+
+/* reads a file, making sure it is terminated with \n \0 */
+extern char* read_file(const char *fn);
+
+extern int get_property(const char *data, char *found, const char *searchkey,
+ const char *not_found);
+extern int check_property(const char *data, const char *prefix);
+#endif
--- /dev/null
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
--- /dev/null
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-17
--- /dev/null
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ tools:context=".MainActivity" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/hello_world" />
+
+</RelativeLayout>
\ No newline at end of file
--- /dev/null
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <item
+ android:id="@+id/action_settings"
+ android:orderInCategory="100"
+ android:showAsAction="never"
+ android:title="@string/action_settings"/>
+
+</menu>
\ No newline at end of file
--- /dev/null
+<resources>
+
+ <!--
+ Customize dimensions originally defined in res/values/dimens.xml (such as
+ screen margins) for sw600dp devices (e.g. 7" tablets) here.
+ -->
+
+</resources>
\ No newline at end of file
--- /dev/null
+<resources>
+
+ <!--
+ Customize dimensions originally defined in res/values/dimens.xml (such as
+ screen margins) for sw720dp devices (e.g. 10" tablets) in landscape here.
+ -->
+ <dimen name="activity_horizontal_margin">128dp</dimen>
+
+</resources>
\ No newline at end of file
--- /dev/null
+<resources>
+
+ <!--
+ Base application theme for API 11+. This theme completely replaces
+ AppBaseTheme from res/values/styles.xml on API 11+ devices.
+ -->
+ <style name="AppBaseTheme" parent="android:Theme.Holo.Light">
+ <!-- API 11 theme customizations can go here. -->
+ </style>
+
+</resources>
\ No newline at end of file
--- /dev/null
+<resources>
+
+ <!--
+ Base application theme for API 14+. This theme completely replaces
+ AppBaseTheme from BOTH res/values/styles.xml and
+ res/values-v11/styles.xml on API 14+ devices.
+ -->
+ <style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+ <!-- API 14 theme customizations can go here. -->
+ </style>
+
+</resources>
\ No newline at end of file
--- /dev/null
+<resources>
+
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+
+</resources>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">Superuser</string>
+ <string name="action_settings">Settings</string>
+ <string name="hello_world">Hello world!</string>
+
+</resources>
\ No newline at end of file
--- /dev/null
+<resources>
+
+ <!--
+ Base application theme, dependent on API level. This theme is replaced
+ by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+ -->
+ <style name="AppBaseTheme" parent="android:Theme.Light">
+ <!--
+ Theme customizations available in newer API levels can go in
+ res/values-vXX/styles.xml, while customizations related to
+ backward-compatibility can go here.
+ -->
+ </style>
+
+ <!-- Application theme. -->
+ <style name="AppTheme" parent="AppBaseTheme">
+ <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+ </style>
+
+</resources>
\ No newline at end of file
--- /dev/null
+package com.koushikdutta.superuser;
+
+import android.os.Bundle;
+import android.app.Activity;
+import android.view.Menu;
+
+public class MainActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.main, menu);
+ return true;
+ }
+
+}
--- /dev/null
+package com.koushikdutta.superuser;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+public class SuReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Bundle b = intent.getExtras();
+ for (String key: b.keySet()) {
+ System.out.println(key);
+ System.out.println("" + b.getString(key));
+
+ }
+ }
+
+}