/* ** ** 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 * * 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 '' 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 */ __noreturn static void panic(const char* format, ...) { va_list args; int e = errno; fprintf(stderr, "%s: ", PROGNAME); va_start(args, format); vfprintf(stderr, format, args); va_end(args); exit(e ? -e : 1); } static void usage(void) { panic("Usage:\n " PROGNAME " [--user ] []\n"); } int main(int argc, char **argv) { const char* pkgname; uid_t myuid, uid, gid, userAppId = 0; int commandArgvOfs = 2, userId = 0; PackageInfo info; struct __user_cap_header_struct capheader; struct __user_cap_data_struct capdata[2]; /* 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"); } memset(&capheader, 0, sizeof(capheader)); memset(&capdata, 0, sizeof(capdata)); capheader.version = _LINUX_CAPABILITY_VERSION_3; capdata[CAP_TO_INDEX(CAP_SETUID)].effective |= CAP_TO_MASK(CAP_SETUID); capdata[CAP_TO_INDEX(CAP_SETGID)].effective |= CAP_TO_MASK(CAP_SETGID); capdata[CAP_TO_INDEX(CAP_SETUID)].permitted |= CAP_TO_MASK(CAP_SETUID); capdata[CAP_TO_INDEX(CAP_SETGID)].permitted |= CAP_TO_MASK(CAP_SETGID); if (capset(&capheader, &capdata[0]) < 0) { panic("Could not set capabilities: %s\n", strerror(errno)); } pkgname = argv[1]; /* get user_id from command line if provided */ if ((argc >= 4) && !strcmp(argv[2], "--user")) { userId = atoi(argv[3]); if (userId < 0) panic("Negative user id %d is provided\n", userId); commandArgvOfs += 2; } /* retrieve package information from system (does setegid) */ if (get_package_info(pkgname, userId, &info) < 0) { panic("Package '%s' is unknown\n", pkgname); } /* verify that user id is not too big. */ if ((UID_MAX - info.uid) / AID_USER < (uid_t)userId) { panic("User id %d is too big\n", userId); } /* calculate user app ID. */ userAppId = (AID_USER * userId) + info.uid; /* reject system packages */ if (userAppId < AID_APP) { panic("Package '%s' is not an application\n", pkgname); } /* reject any non-debuggable package */ if (!info.isDebuggable) { panic("Package '%s' is not debuggable\n", pkgname); } /* check that the data directory path is valid */ if (check_data_path(info.dataDir, userAppId) < 0) { panic("Package '%s' has corrupt installation\n", pkgname); } /* Ensure that we change all real/effective/saved IDs at the * same time to avoid nasty surprises. */ uid = gid = userAppId; if(setresgid(gid,gid,gid) || setresuid(uid,uid,uid)) { panic("Permission denied\n"); } /* Required if caller has uid and gid all non-zero */ memset(&capdata, 0, sizeof(capdata)); if (capset(&capheader, &capdata[0]) < 0) { panic("Could not clear all capabilities: %s\n", strerror(errno)); } if (selinux_android_setcontext(uid, 0, info.seinfo, pkgname) < 0) { panic("Could not set SELinux security context: %s\n", strerror(errno)); } // cd into the data directory, and set $HOME correspondingly. if (TEMP_FAILURE_RETRY(chdir(info.dataDir)) < 0) { panic("Could not cd to package's data directory: %s\n", strerror(errno)); } setenv("HOME", info.dataDir, 1); // Reset parts of the environment, like su would. setenv("PATH", _PATH_DEFPATH, 1); unsetenv("IFS"); // Set the user-specific parts for this user. struct passwd* pw = getpwuid(uid); setenv("LOGNAME", pw->pw_name, 1); setenv("SHELL", pw->pw_shell, 1); setenv("USER", pw->pw_name, 1); /* User specified command for exec. */ if ((argc >= commandArgvOfs + 1) && (execvp(argv[commandArgvOfs], argv+commandArgvOfs) < 0)) { panic("exec failed for %s: %s\n", argv[commandArgvOfs], strerror(errno)); } /* Default exec shell. */ execlp("/system/bin/sh", "sh", NULL); panic("exec failed: %s\n", strerror(errno)); }