/* ** ** Copyright 2010, 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 ** ** 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. */ #define PROGNAME "run-as" #define LOG_TAG PROGNAME #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <dirent.h> #include <errno.h> #include <unistd.h> #include <time.h> #include <stdarg.h> #include <selinux/android.h> #include <private/android_filesystem_config.h> #include "package.h" /* * WARNING WARNING WARNING WARNING * * This program runs with CAP_SETUID and CAP_SETGID capabilities on Android * production devices. Be very conservative when modifying it to avoid any * serious security issue. Keep in mind the following: * * - This program should only run for the 'root' or 'shell' users * * - Avoid anything that is more complex than simple system calls * until the uid/gid has been dropped to that of a normal user * or you are sure to exit. * * This avoids depending on environment variables, system properties * and other external factors that may affect the C library in * unpredictable ways. * * - Do not trust user input and/or the filesystem whenever possible. * * Read README.TXT for more details. * * * * The purpose of this program is to run a command as a specific * application user-id. Typical usage is: * * run-as <package-name> <command> <args> * * The 'run-as' binary is installed with CAP_SETUID and CAP_SETGID file * capabilities, but will check the following: * * - that it is invoked from the 'shell' or 'root' user (abort otherwise) * - that '<package-name>' is the name of an installed and debuggable package * - that the package's data directory is well-formed (see package.c) * * If so, it will drop to the application's user id / group id, cd to the * package's data directory, then run the command there. * * NOTE: In the future it might not be possible to cd to the package's data * directory under that package's user id / group id, in which case this * utility will need to be changed accordingly. * * This can be useful for a number of different things on production devices: * * - Allow application developers to look at their own applicative data * during development. * * - Run the 'gdbserver' binary executable to allow native debugging */ static void usage(void) { const char* str = "Usage: " PROGNAME " <package-name> <command> [<args>]\n\n"; write(1, str, strlen(str)); exit(1); } static void panic(const char* format, ...) { va_list args; fprintf(stderr, "%s: ", PROGNAME); va_start(args, format); vfprintf(stderr, format, args); va_end(args); exit(1); } int main(int argc, char **argv) { const char* pkgname; int myuid, uid, gid; PackageInfo info; /* check arguments */ if (argc < 2) usage(); /* check userid of caller - must be 'shell' or 'root' */ myuid = getuid(); if (myuid != AID_SHELL && myuid != AID_ROOT) { panic("only 'shell' or 'root' users can run this program\n"); } /* retrieve package information from system */ pkgname = argv[1]; if (get_package_info(pkgname, &info) < 0) { panic("Package '%s' is unknown\n", pkgname); return 1; } /* reject system packages */ if (info.uid < AID_APP) { panic("Package '%s' is not an application\n", pkgname); return 1; } /* reject any non-debuggable package */ if (!info.isDebuggable) { panic("Package '%s' is not debuggable\n", pkgname); return 1; } /* check that the data directory path is valid */ if (check_data_path(info.dataDir, info.uid) < 0) { panic("Package '%s' has corrupt installation\n", pkgname); return 1; } /* Ensure that we change all real/effective/saved IDs at the * same time to avoid nasty surprises. */ uid = gid = info.uid; if(setresgid(gid,gid,gid) || setresuid(uid,uid,uid)) { panic("Permission denied\n"); return 1; } if (selinux_android_setcontext(uid, 0, info.seinfo, pkgname) < 0) { panic("Could not set SELinux security context: %s\n", strerror(errno)); return 1; } /* cd into the data directory */ { int ret; do { ret = chdir(info.dataDir); } while (ret < 0 && errno == EINTR); if (ret < 0) { panic("Could not cd to package's data directory: %s\n", strerror(errno)); return 1; } } /* User specified command for exec. */ if (argc >= 3 ) { if (execvp(argv[2], argv+2) < 0) { panic("exec failed for %s Error:%s\n", argv[2], strerror(errno)); return -errno; } } /* Default exec shell. */ execlp("/system/bin/sh", "sh", NULL); panic("exec failed\n"); return 1; }