MagiskSU rewrite for unified binary
This commit is contained in:
parent
ed052e0b0b
commit
a92c9fc226
35
activity.c
35
activity.c
@ -3,17 +3,6 @@
|
|||||||
** Copyright 2010, Adam Shanks (@ChainsDD)
|
** Copyright 2010, Adam Shanks (@ChainsDD)
|
||||||
** Copyright 2008, Zinx Verituse (@zinxv)
|
** 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/types.h>
|
||||||
@ -25,6 +14,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
#include "magisk.h"
|
||||||
#include "su.h"
|
#include "su.h"
|
||||||
|
|
||||||
/* intent actions */
|
/* intent actions */
|
||||||
@ -34,18 +24,16 @@
|
|||||||
|
|
||||||
#define AM_PATH "/system/bin/app_process", "/system/bin", "com.android.commands.am.Am"
|
#define AM_PATH "/system/bin/app_process", "/system/bin", "com.android.commands.am.Am"
|
||||||
|
|
||||||
// TODO: leverage this with exec_log?
|
static void silent_run(char* const args[]) {
|
||||||
int silent_run(char* const args[]) {
|
|
||||||
set_identity(0);
|
set_identity(0);
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
pid = fork();
|
pid = fork();
|
||||||
/* Parent */
|
/* Parent */
|
||||||
if (pid < 0) {
|
if (pid < 0) {
|
||||||
PLOGE("fork");
|
PLOGE("fork");
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
else if (pid > 0) {
|
else if (pid > 0) {
|
||||||
return 0;
|
return;
|
||||||
}
|
}
|
||||||
int zero = open("/dev/zero", O_RDONLY | O_CLOEXEC);
|
int zero = open("/dev/zero", O_RDONLY | O_CLOEXEC);
|
||||||
dup2(zero, 0);
|
dup2(zero, 0);
|
||||||
@ -56,10 +44,9 @@ int silent_run(char* const args[]) {
|
|||||||
execv(args[0], args);
|
execv(args[0], args);
|
||||||
PLOGE("exec am");
|
PLOGE("exec am");
|
||||||
_exit(EXIT_FAILURE);
|
_exit(EXIT_FAILURE);
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int get_owner_login_user_args(struct su_context *ctx, char* user, int user_len) {
|
static int get_owner_login_user_args(struct su_context *ctx, char* user, int user_len) {
|
||||||
int needs_owner_login_prompt = 0;
|
int needs_owner_login_prompt = 0;
|
||||||
|
|
||||||
if (ctx->user.multiuser_mode == MULTIUSER_MODE_OWNER_MANAGED) {
|
if (ctx->user.multiuser_mode == MULTIUSER_MODE_OWNER_MANAGED) {
|
||||||
@ -81,7 +68,7 @@ int get_owner_login_user_args(struct su_context *ctx, char* user, int user_len)
|
|||||||
return needs_owner_login_prompt;
|
return needs_owner_login_prompt;
|
||||||
}
|
}
|
||||||
|
|
||||||
int send_result(struct su_context *ctx, policy_t policy) {
|
void app_send_result(struct su_context *ctx, policy_t policy) {
|
||||||
char binary_version[256];
|
char binary_version[256];
|
||||||
sprintf(binary_version, "%d", VERSION_CODE);
|
sprintf(binary_version, "%d", VERSION_CODE);
|
||||||
|
|
||||||
@ -148,10 +135,10 @@ int send_result(struct su_context *ctx, policy_t policy) {
|
|||||||
user,
|
user,
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
return silent_run(result_command);
|
silent_run(result_command);
|
||||||
}
|
}
|
||||||
|
|
||||||
int send_request(struct su_context *ctx) {
|
void app_send_request(struct su_context *ctx) {
|
||||||
// if su is operating in MULTIUSER_MODEL_OWNER,
|
// if su is operating in MULTIUSER_MODEL_OWNER,
|
||||||
// and the user requestor is not the owner,
|
// and the user requestor is not the owner,
|
||||||
// the owner needs to be notified of the request.
|
// the owner needs to be notified of the request.
|
||||||
@ -159,7 +146,6 @@ int send_request(struct su_context *ctx) {
|
|||||||
char user[64];
|
char user[64];
|
||||||
int needs_owner_login_prompt = get_owner_login_user_args(ctx, user, sizeof(user));
|
int needs_owner_login_prompt = get_owner_login_user_args(ctx, user, sizeof(user));
|
||||||
|
|
||||||
int ret;
|
|
||||||
if (needs_owner_login_prompt) {
|
if (needs_owner_login_prompt) {
|
||||||
char uid[256];
|
char uid[256];
|
||||||
sprintf(uid, "%d", ctx->from.uid);
|
sprintf(uid, "%d", ctx->from.uid);
|
||||||
@ -179,10 +165,7 @@ int send_request(struct su_context *ctx) {
|
|||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
int ret = silent_run(notify_command);
|
silent_run(notify_command);
|
||||||
if (ret) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
char *request_command[] = {
|
char *request_command[] = {
|
||||||
@ -196,5 +179,5 @@ int send_request(struct su_context *ctx) {
|
|||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
return silent_run(request_command);
|
silent_run(request_command);
|
||||||
}
|
}
|
||||||
|
736
daemon.c
736
daemon.c
@ -1,736 +0,0 @@
|
|||||||
/*
|
|
||||||
** Copyright 2017, John Wu (@topjohnwu)
|
|
||||||
** 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define _GNU_SOURCE /* for unshare() */
|
|
||||||
|
|
||||||
#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 <sys/ioctl.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/mount.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/sendfile.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <sched.h>
|
|
||||||
#include <termios.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <selinux/selinux.h>
|
|
||||||
|
|
||||||
#ifdef SUPERUSER_EMBEDDED
|
|
||||||
#include <cutils/multiuser.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "su.h"
|
|
||||||
#include "utils.h"
|
|
||||||
#include "pts.h"
|
|
||||||
|
|
||||||
int is_daemon = 0;
|
|
||||||
int daemon_from_uid = 0;
|
|
||||||
int daemon_from_pid = 0;
|
|
||||||
|
|
||||||
// Constants for the atty bitfield
|
|
||||||
#define ATTY_IN 1
|
|
||||||
#define ATTY_OUT 2
|
|
||||||
#define ATTY_ERR 4
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Receive a file descriptor from a Unix socket.
|
|
||||||
* Contributed by @mkasick
|
|
||||||
*
|
|
||||||
* Returns the file descriptor on success, or -1 if a file
|
|
||||||
* descriptor was not actually included in the message
|
|
||||||
*
|
|
||||||
* On error the function terminates by calling exit(-1)
|
|
||||||
*/
|
|
||||||
static int recv_fd(int sockfd) {
|
|
||||||
// Need to receive data from the message, otherwise don't care about it.
|
|
||||||
char iovbuf;
|
|
||||||
|
|
||||||
struct iovec iov = {
|
|
||||||
.iov_base = &iovbuf,
|
|
||||||
.iov_len = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
char cmsgbuf[CMSG_SPACE(sizeof(int))];
|
|
||||||
|
|
||||||
struct msghdr msg = {
|
|
||||||
.msg_iov = &iov,
|
|
||||||
.msg_iovlen = 1,
|
|
||||||
.msg_control = cmsgbuf,
|
|
||||||
.msg_controllen = sizeof(cmsgbuf),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (recvmsg(sockfd, &msg, MSG_WAITALL) != 1) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Was a control message actually sent?
|
|
||||||
switch (msg.msg_controllen) {
|
|
||||||
case 0:
|
|
||||||
// No, so the file descriptor was closed and won't be used.
|
|
||||||
return -1;
|
|
||||||
case sizeof(cmsgbuf):
|
|
||||||
// Yes, grab the file descriptor from it.
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
|
|
||||||
|
|
||||||
if (cmsg == NULL ||
|
|
||||||
cmsg->cmsg_len != CMSG_LEN(sizeof(int)) ||
|
|
||||||
cmsg->cmsg_level != SOL_SOCKET ||
|
|
||||||
cmsg->cmsg_type != SCM_RIGHTS) {
|
|
||||||
error:
|
|
||||||
LOGE("unable to read fd");
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return *(int *)CMSG_DATA(cmsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Send a file descriptor through a Unix socket.
|
|
||||||
* Contributed by @mkasick
|
|
||||||
*
|
|
||||||
* On error the function terminates by calling exit(-1)
|
|
||||||
*
|
|
||||||
* fd may be -1, in which case the dummy data is sent,
|
|
||||||
* but no control message with the FD is sent.
|
|
||||||
*/
|
|
||||||
static void send_fd(int sockfd, int fd) {
|
|
||||||
// Need to send some data in the message, this will do.
|
|
||||||
struct iovec iov = {
|
|
||||||
.iov_base = "",
|
|
||||||
.iov_len = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct msghdr msg = {
|
|
||||||
.msg_iov = &iov,
|
|
||||||
.msg_iovlen = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
char cmsgbuf[CMSG_SPACE(sizeof(int))];
|
|
||||||
|
|
||||||
if (fd != -1) {
|
|
||||||
// Is the file descriptor actually open?
|
|
||||||
if (fcntl(fd, F_GETFD) == -1) {
|
|
||||||
if (errno != EBADF) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
// It's closed, don't send a control message or sendmsg will EBADF.
|
|
||||||
} else {
|
|
||||||
// It's open, send the file descriptor in a control message.
|
|
||||||
msg.msg_control = cmsgbuf;
|
|
||||||
msg.msg_controllen = sizeof(cmsgbuf);
|
|
||||||
|
|
||||||
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
|
|
||||||
|
|
||||||
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
|
|
||||||
cmsg->cmsg_level = SOL_SOCKET;
|
|
||||||
cmsg->cmsg_type = SCM_RIGHTS;
|
|
||||||
|
|
||||||
*(int *)CMSG_DATA(cmsg) = fd;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sendmsg(sockfd, &msg, 0) != 1) {
|
|
||||||
error:
|
|
||||||
PLOGE("unable to send fd");
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int read_int(int fd) {
|
|
||||||
int val;
|
|
||||||
int len = read(fd, &val, sizeof(int));
|
|
||||||
if (len != sizeof(int)) {
|
|
||||||
LOGE("unable to read int: %d", len);
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void write_int(int fd, int val) {
|
|
||||||
int written = write(fd, &val, sizeof(int));
|
|
||||||
if (written != sizeof(int)) {
|
|
||||||
PLOGE("unable to write int");
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static char* read_string(int fd) {
|
|
||||||
int len = read_int(fd);
|
|
||||||
if (len > PATH_MAX || len < 0) {
|
|
||||||
LOGE("invalid string length %d", len);
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
char* val = malloc(sizeof(char) * (len + 1));
|
|
||||||
if (val == NULL) {
|
|
||||||
LOGE("unable to malloc string");
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
val[len] = '\0';
|
|
||||||
int amount = read(fd, val, len);
|
|
||||||
if (amount != len) {
|
|
||||||
LOGE("unable to read string");
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void write_string(int fd, char* val) {
|
|
||||||
int len = strlen(val);
|
|
||||||
write_int(fd, len);
|
|
||||||
int written = write(fd, val, len);
|
|
||||||
if (written != len) {
|
|
||||||
PLOGE("unable to write string");
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef SUPERUSER_EMBEDDED
|
|
||||||
static void mount_emulated_storage(int user_id) {
|
|
||||||
const char *emulated_source = getenv("EMULATED_STORAGE_SOURCE");
|
|
||||||
const char *emulated_target = getenv("EMULATED_STORAGE_TARGET");
|
|
||||||
const char* legacy = getenv("EXTERNAL_STORAGE");
|
|
||||||
|
|
||||||
if (!emulated_source || !emulated_target) {
|
|
||||||
// No emulated storage is present
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a second private mount namespace for our process
|
|
||||||
if (unshare(CLONE_NEWNS) < 0) {
|
|
||||||
PLOGE("unshare");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mount("rootfs", "/", NULL, MS_SLAVE | MS_REC, NULL) < 0) {
|
|
||||||
PLOGE("mount rootfs as slave");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// /mnt/shell/emulated -> /storage/emulated
|
|
||||||
if (mount(emulated_source, emulated_target, NULL, MS_BIND, NULL) < 0) {
|
|
||||||
PLOGE("mount emulated storage");
|
|
||||||
}
|
|
||||||
|
|
||||||
char target_user[PATH_MAX];
|
|
||||||
snprintf(target_user, PATH_MAX, "%s/%d", emulated_target, user_id);
|
|
||||||
|
|
||||||
// /mnt/shell/emulated/<user> -> /storage/emulated/legacy
|
|
||||||
if (mount(target_user, legacy, NULL, MS_BIND | MS_REC, NULL) < 0) {
|
|
||||||
PLOGE("mount legacy path");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static int run_daemon_child(int infd, int outfd, int errfd, int argc, char** argv) {
|
|
||||||
if (-1 == dup2(outfd, STDOUT_FILENO)) {
|
|
||||||
PLOGE("dup2 child outfd");
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-1 == dup2(errfd, STDERR_FILENO)) {
|
|
||||||
PLOGE("dup2 child errfd");
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-1 == dup2(infd, STDIN_FILENO)) {
|
|
||||||
PLOGE("dup2 child infd");
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
close(infd);
|
|
||||||
close(outfd);
|
|
||||||
close(errfd);
|
|
||||||
|
|
||||||
return su_main_nodaemon(argc, argv);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int daemon_accept(int fd) {
|
|
||||||
is_daemon = 1;
|
|
||||||
int pid = read_int(fd);
|
|
||||||
LOGD("remote pid: %d", pid);
|
|
||||||
char *pts_slave = read_string(fd);
|
|
||||||
LOGD("remote pts_slave: %s", pts_slave);
|
|
||||||
daemon_from_uid = read_int(fd);
|
|
||||||
LOGD("remote uid: %d", daemon_from_uid);
|
|
||||||
daemon_from_pid = read_int(fd);
|
|
||||||
LOGD("remote req pid: %d", daemon_from_pid);
|
|
||||||
int mount_storage = read_int(fd);
|
|
||||||
char *cwd = read_string(fd);
|
|
||||||
LOGD("remote cwd: %s", cwd);
|
|
||||||
|
|
||||||
struct ucred credentials;
|
|
||||||
socklen_t ucred_length = sizeof(struct ucred);
|
|
||||||
/* fill in the user data structure */
|
|
||||||
if(getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &credentials, &ucred_length)) {
|
|
||||||
LOGE("could obtain credentials from unix domain socket");
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
// if the credentials on the other side of the wire are NOT root,
|
|
||||||
// we can't trust anything being sent.
|
|
||||||
if (credentials.uid != 0) {
|
|
||||||
daemon_from_uid = credentials.uid;
|
|
||||||
pid = credentials.pid;
|
|
||||||
daemon_from_pid = credentials.pid;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The the FDs for each of the streams
|
|
||||||
int infd = recv_fd(fd);
|
|
||||||
int outfd = recv_fd(fd);
|
|
||||||
int errfd = recv_fd(fd);
|
|
||||||
|
|
||||||
int argc = read_int(fd);
|
|
||||||
if (argc < 0 || argc > 512) {
|
|
||||||
LOGE("unable to allocate args: %d", argc);
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
LOGD("remote args: %d", argc);
|
|
||||||
char** argv = (char**)malloc(sizeof(char*) * (argc + 1));
|
|
||||||
argv[argc] = NULL;
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < argc; i++) {
|
|
||||||
argv[i] = read_string(fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ack
|
|
||||||
write_int(fd, 1);
|
|
||||||
|
|
||||||
// Fork the child process. The fork has to happen before calling
|
|
||||||
// setsid() and opening the pseudo-terminal so that the parent
|
|
||||||
// is not affected
|
|
||||||
int child = fork();
|
|
||||||
if (child < 0) {
|
|
||||||
// fork failed, send a return code and bail out
|
|
||||||
PLOGE("unable to fork");
|
|
||||||
write(fd, &child, sizeof(int));
|
|
||||||
close(fd);
|
|
||||||
return child;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (child != 0) {
|
|
||||||
// In parent, wait for the child to exit, and send the exit code
|
|
||||||
// across the wire.
|
|
||||||
int status, code;
|
|
||||||
|
|
||||||
free(pts_slave);
|
|
||||||
|
|
||||||
LOGD("waiting for child exit");
|
|
||||||
if (waitpid(child, &status, 0) > 0) {
|
|
||||||
code = WEXITSTATUS(status);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
code = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass the return code back to the client
|
|
||||||
LOGD("sending code");
|
|
||||||
if (write(fd, &code, sizeof(int)) != sizeof(int)) {
|
|
||||||
PLOGE("unable to write exit code");
|
|
||||||
}
|
|
||||||
|
|
||||||
close(fd);
|
|
||||||
LOGD("child exited");
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are in the child now
|
|
||||||
// Close the unix socket file descriptor
|
|
||||||
close (fd);
|
|
||||||
|
|
||||||
// Become session leader
|
|
||||||
if (setsid() == (pid_t) -1) {
|
|
||||||
PLOGE("setsid");
|
|
||||||
}
|
|
||||||
|
|
||||||
int ptsfd;
|
|
||||||
if (pts_slave[0]) {
|
|
||||||
//Check pts_slave file is owned by daemon_from_uid
|
|
||||||
{
|
|
||||||
struct stat stbuf;
|
|
||||||
int res = stat(pts_slave, &stbuf);
|
|
||||||
if(res) {
|
|
||||||
PLOGE("stat(pts_slave) daemon");
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
//If caller is not root, ensure the owner of pts_slave is the caller
|
|
||||||
if(stbuf.st_uid != credentials.uid &&
|
|
||||||
credentials.uid != 0) {
|
|
||||||
PLOGE("Wrong permission of pts_slave");
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Opening the TTY has to occur after the
|
|
||||||
// fork() and setsid() so that it becomes
|
|
||||||
// our controlling TTY and not the daemon's
|
|
||||||
ptsfd = open(pts_slave, O_RDWR);
|
|
||||||
if (ptsfd == -1) {
|
|
||||||
PLOGE("open(pts_slave) daemon");
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Check we haven't been fooled
|
|
||||||
{
|
|
||||||
struct stat stbuf;
|
|
||||||
int res = fstat(ptsfd, &stbuf);
|
|
||||||
if(res) {
|
|
||||||
//If we have been fooled DO NOT WRITE ANYTHING
|
|
||||||
_exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(stbuf.st_uid != credentials.uid &&
|
|
||||||
credentials.uid != 0) {
|
|
||||||
_exit(2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (infd < 0) {
|
|
||||||
LOGD("daemon: stdin using PTY");
|
|
||||||
infd = ptsfd;
|
|
||||||
}
|
|
||||||
if (outfd < 0) {
|
|
||||||
LOGD("daemon: stdout using PTY");
|
|
||||||
outfd = ptsfd;
|
|
||||||
}
|
|
||||||
if (errfd < 0) {
|
|
||||||
LOGD("daemon: stderr using PTY");
|
|
||||||
errfd = ptsfd;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If a TTY was sent directly, make it the CTTY.
|
|
||||||
if (isatty(infd)) {
|
|
||||||
ioctl(infd, TIOCSCTTY, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free(pts_slave);
|
|
||||||
|
|
||||||
#ifdef SUPERUSER_EMBEDDED
|
|
||||||
if (mount_storage) {
|
|
||||||
mount_emulated_storage(multiuser_get_user_id(daemon_from_uid));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Change directory to cwd
|
|
||||||
chdir(cwd);
|
|
||||||
free(cwd);
|
|
||||||
|
|
||||||
return run_daemon_child(infd, outfd, errfd, argc, argv);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void unlock_blocks() {
|
|
||||||
#define DEV_BLOCKS "/dev/block"
|
|
||||||
char path[PATH_MAX];
|
|
||||||
DIR *dir;
|
|
||||||
struct dirent *entry;
|
|
||||||
int fd, OFF = 0;
|
|
||||||
|
|
||||||
if (!(dir = opendir(DEV_BLOCKS)))
|
|
||||||
return;
|
|
||||||
|
|
||||||
while((entry = readdir(dir))) {
|
|
||||||
if (entry->d_type == DT_BLK && strstr(entry->d_name, "mmc") != NULL) {
|
|
||||||
sprintf(path, "%s/%s", DEV_BLOCKS, entry->d_name);
|
|
||||||
fd = open(path, O_RDONLY);
|
|
||||||
if (fd < 0) {
|
|
||||||
PLOGE("open");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (ioctl(fd, BLKROSET, &OFF) == -1)
|
|
||||||
PLOGE("ioctl");
|
|
||||||
close(fd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
closedir(dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
int run_daemon() {
|
|
||||||
if (getuid() != AID_ROOT || getgid() != AID_ROOT) {
|
|
||||||
PLOGE("daemon requires root. uid/gid not root");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (fork()) {
|
|
||||||
case 0:
|
|
||||||
break;
|
|
||||||
case -1:
|
|
||||||
PLOGE("fork");
|
|
||||||
return -1;
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setsid() < 0) {
|
|
||||||
PLOGE("setsid");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (setcon("u:r:su:s0") < 0) {
|
|
||||||
PLOGE("setcon");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
unlock_blocks();
|
|
||||||
|
|
||||||
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_LOCAL;
|
|
||||||
strcpy(sun.sun_path, REQUESTOR_DAEMON_PATH);
|
|
||||||
|
|
||||||
unlink(sun.sun_path);
|
|
||||||
|
|
||||||
if (bind(fd, (struct sockaddr*)&sun, sizeof(sun)) < 0) {
|
|
||||||
PLOGE("daemon bind");
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
|
|
||||||
chmod(sun.sun_path, 0777);
|
|
||||||
|
|
||||||
if (listen(fd, 10) < 0) {
|
|
||||||
PLOGE("daemon listen");
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
|
|
||||||
int client;
|
|
||||||
while ((client = accept(fd, NULL, NULL)) > 0) {
|
|
||||||
if (fork_zero_fucks() == 0) {
|
|
||||||
close(fd);
|
|
||||||
return daemon_accept(client);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
close(client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGE("daemon exiting");
|
|
||||||
err:
|
|
||||||
close(fd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// List of signals which cause process termination
|
|
||||||
static int quit_signals[] = { SIGALRM, SIGHUP, SIGPIPE, SIGQUIT, SIGTERM, SIGINT, 0 };
|
|
||||||
|
|
||||||
static void sighandler(int sig) {
|
|
||||||
(void)sig;
|
|
||||||
restore_stdin();
|
|
||||||
|
|
||||||
// Assume we'll only be called before death
|
|
||||||
// See note before sigaction() in set_stdin_raw()
|
|
||||||
//
|
|
||||||
// Now, close all standard I/O to cause the pumps
|
|
||||||
// to exit so we can continue and retrieve the exit
|
|
||||||
// code
|
|
||||||
close(STDIN_FILENO);
|
|
||||||
close(STDOUT_FILENO);
|
|
||||||
close(STDERR_FILENO);
|
|
||||||
|
|
||||||
// Put back all the default handlers
|
|
||||||
struct sigaction act;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
memset(&act, '\0', sizeof(act));
|
|
||||||
act.sa_handler = SIG_DFL;
|
|
||||||
for (i = 0; quit_signals[i]; i++) {
|
|
||||||
if (sigaction(quit_signals[i], &act, NULL) < 0) {
|
|
||||||
PLOGE("Error removing signal handler");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setup signal handlers trap signals which should result in program termination
|
|
||||||
* so that we can restore the terminal to its normal state and retrieve the
|
|
||||||
* return code.
|
|
||||||
*/
|
|
||||||
static void setup_sighandlers(void) {
|
|
||||||
struct sigaction act;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
// Install the termination handlers
|
|
||||||
// Note: we're assuming that none of these signal handlers are already trapped.
|
|
||||||
// If they are, we'll need to modify this code to save the previous handler and
|
|
||||||
// call it after we restore stdin to its previous state.
|
|
||||||
memset(&act, '\0', sizeof(act));
|
|
||||||
act.sa_handler = &sighandler;
|
|
||||||
for (i = 0; quit_signals[i]; i++) {
|
|
||||||
if (sigaction(quit_signals[i], &act, NULL) < 0) {
|
|
||||||
PLOGE("Error installing signal handler");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int connect_daemon(int argc, char *argv[], int ppid) {
|
|
||||||
int uid = getuid();
|
|
||||||
int ptmx = -1;
|
|
||||||
char pts_slave[PATH_MAX];
|
|
||||||
char cwd[PATH_MAX];
|
|
||||||
getcwd(cwd, sizeof(cwd));
|
|
||||||
|
|
||||||
struct sockaddr_un sun;
|
|
||||||
|
|
||||||
// Open a socket to the daemon
|
|
||||||
int socketfd = socket(AF_LOCAL, SOCK_STREAM, 0);
|
|
||||||
if (socketfd < 0) {
|
|
||||||
PLOGE("socket");
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
if (fcntl(socketfd, F_SETFD, FD_CLOEXEC)) {
|
|
||||||
PLOGE("fcntl FD_CLOEXEC");
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(&sun, 0, sizeof(sun));
|
|
||||||
sun.sun_family = AF_LOCAL;
|
|
||||||
strcpy(sun.sun_path, REQUESTOR_DAEMON_PATH);
|
|
||||||
|
|
||||||
if (0 != connect(socketfd, (struct sockaddr*)&sun, sizeof(sun))) {
|
|
||||||
PLOGE("connect");
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGD("connecting client %d", getpid());
|
|
||||||
|
|
||||||
int mount_storage = getenv("MOUNT_EMULATED_STORAGE") != NULL;
|
|
||||||
|
|
||||||
// Determine which one of our streams are attached to a TTY
|
|
||||||
int atty = 0;
|
|
||||||
|
|
||||||
// Send TTYs directly (instead of proxying with a PTY) if
|
|
||||||
// the SUPERUSER_SEND_TTY environment variable is set.
|
|
||||||
if (getenv("SUPERUSER_SEND_TTY") == NULL) {
|
|
||||||
if (isatty(STDIN_FILENO)) atty |= ATTY_IN;
|
|
||||||
if (isatty(STDOUT_FILENO)) atty |= ATTY_OUT;
|
|
||||||
if (isatty(STDERR_FILENO)) atty |= ATTY_ERR;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (atty) {
|
|
||||||
// We need a PTY. Get one.
|
|
||||||
ptmx = pts_open(pts_slave, sizeof(pts_slave));
|
|
||||||
if (ptmx < 0) {
|
|
||||||
PLOGE("pts_open");
|
|
||||||
exit(-1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pts_slave[0] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send some info to the daemon, starting with our PID
|
|
||||||
write_int(socketfd, getpid());
|
|
||||||
// Send the slave path to the daemon
|
|
||||||
// (This is "" if we're not using PTYs)
|
|
||||||
write_string(socketfd, pts_slave);
|
|
||||||
// User ID
|
|
||||||
write_int(socketfd, uid);
|
|
||||||
// Parent PID
|
|
||||||
write_int(socketfd, ppid);
|
|
||||||
write_int(socketfd, mount_storage);
|
|
||||||
// CWD
|
|
||||||
write_string(socketfd, cwd);
|
|
||||||
|
|
||||||
// Send stdin
|
|
||||||
if (atty & ATTY_IN) {
|
|
||||||
// Using PTY
|
|
||||||
send_fd(socketfd, -1);
|
|
||||||
} else {
|
|
||||||
send_fd(socketfd, STDIN_FILENO);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send stdout
|
|
||||||
if (atty & ATTY_OUT) {
|
|
||||||
// Forward SIGWINCH
|
|
||||||
watch_sigwinch_async(STDOUT_FILENO, ptmx);
|
|
||||||
|
|
||||||
// Using PTY
|
|
||||||
send_fd(socketfd, -1);
|
|
||||||
} else {
|
|
||||||
send_fd(socketfd, STDOUT_FILENO);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send stderr
|
|
||||||
if (atty & ATTY_ERR) {
|
|
||||||
// Using PTY
|
|
||||||
send_fd(socketfd, -1);
|
|
||||||
} else {
|
|
||||||
send_fd(socketfd, STDERR_FILENO);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Number of command line arguments
|
|
||||||
write_int(socketfd, mount_storage ? argc - 1 : argc);
|
|
||||||
|
|
||||||
// Command line arguments
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < argc; i++) {
|
|
||||||
if (i == 1 && mount_storage) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
write_string(socketfd, argv[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for acknowledgement from daemon
|
|
||||||
read_int(socketfd);
|
|
||||||
|
|
||||||
if (atty & ATTY_IN) {
|
|
||||||
setup_sighandlers();
|
|
||||||
pump_stdin_async(ptmx);
|
|
||||||
}
|
|
||||||
if (atty & ATTY_OUT) {
|
|
||||||
pump_stdout_blocking(ptmx);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the exit code
|
|
||||||
int code = read_int(socketfd);
|
|
||||||
close(socketfd);
|
|
||||||
LOGD("client exited %d", code);
|
|
||||||
|
|
||||||
return code;
|
|
||||||
}
|
|
13
db.c
13
db.c
@ -1,17 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
** Copyright 2013, Koushik Dutta (@koush)
|
** Copyright 2013, Koushik Dutta (@koush)
|
||||||
**
|
**
|
||||||
** 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 <stdlib.h>
|
||||||
@ -19,7 +8,9 @@
|
|||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "magisk.h"
|
||||||
#include "su.h"
|
#include "su.h"
|
||||||
|
|
||||||
struct callback_data_t {
|
struct callback_data_t {
|
||||||
|
54
misc.c
Normal file
54
misc.c
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
/* misc.c - Miscellaneous stuffs for su
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include "magisk.h"
|
||||||
|
#include "su.h"
|
||||||
|
|
||||||
|
int quit_signals[] = { SIGALRM, SIGABRT, SIGHUP, SIGPIPE, SIGQUIT, SIGTERM, SIGINT, 0 };
|
||||||
|
|
||||||
|
void setup_sighandlers(void (*handler)(int)) {
|
||||||
|
struct sigaction act;
|
||||||
|
|
||||||
|
// Install the termination handlers
|
||||||
|
// Note: we're assuming that none of these signal handlers are already trapped.
|
||||||
|
// If they are, we'll need to modify this code to save the previous handler and
|
||||||
|
// call it after we restore stdin to its previous state.
|
||||||
|
memset(&act, 0, sizeof(act));
|
||||||
|
act.sa_handler = handler;
|
||||||
|
for (int i = 0; quit_signals[i]; ++i) {
|
||||||
|
sigaction(quit_signals[i], &act, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_identity(unsigned uid) {
|
||||||
|
/*
|
||||||
|
* Set effective uid back to root, otherwise setres[ug]id will fail
|
||||||
|
* if uid isn't root.
|
||||||
|
*/
|
||||||
|
if (seteuid(0)) {
|
||||||
|
PLOGE("seteuid (root)");
|
||||||
|
}
|
||||||
|
if (setresgid(uid, uid, uid)) {
|
||||||
|
PLOGE("setresgid (%u)", uid);
|
||||||
|
}
|
||||||
|
if (setresuid(uid, uid, uid)) {
|
||||||
|
PLOGE("setresuid (%u)", uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char *get_command(const struct su_request *to) {
|
||||||
|
if (to->command)
|
||||||
|
return to->command;
|
||||||
|
if (to->shell)
|
||||||
|
return to->shell;
|
||||||
|
return DEFAULT_SHELL;
|
||||||
|
}
|
||||||
|
|
40
pts.c
40
pts.c
@ -1,17 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2013, Tan Chee Eng (@tan-ce)
|
* Copyright 2013, Tan Chee Eng (@tan-ce)
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -21,6 +9,7 @@
|
|||||||
* helper functions to handle raw input mode and terminal window resizing
|
* helper functions to handle raw input mode and terminal window resizing
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#define _GNU_SOURCE
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
@ -29,6 +18,7 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
|
||||||
|
#include "magisk.h"
|
||||||
#include "pts.h"
|
#include "pts.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -110,27 +100,27 @@ int pts_open(char *slave_name, size_t slave_name_size) {
|
|||||||
|
|
||||||
// Open master ptmx device
|
// Open master ptmx device
|
||||||
fdm = open("/dev/ptmx", O_RDWR);
|
fdm = open("/dev/ptmx", O_RDWR);
|
||||||
if (fdm == -1) return -1;
|
if (fdm == -1)
|
||||||
|
goto error;
|
||||||
|
|
||||||
// Get the slave name
|
// Get the slave name
|
||||||
if (ptsname_r(fdm, slave_name, slave_name_size-1)) {
|
if (ptsname_r(fdm, slave_name, slave_name_size-1))
|
||||||
close(fdm);
|
goto error;
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
|
|
||||||
slave_name[slave_name_size - 1] = '\0';
|
slave_name[slave_name_size - 1] = '\0';
|
||||||
|
|
||||||
// Grant, then unlock
|
// Grant, then unlock
|
||||||
if (grantpt(fdm) == -1) {
|
if (grantpt(fdm) == -1)
|
||||||
close(fdm);
|
goto error;
|
||||||
return -1;
|
|
||||||
}
|
if (unlockpt(fdm) == -1)
|
||||||
if (unlockpt(fdm) == -1) {
|
goto error;
|
||||||
close(fdm);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return fdm;
|
return fdm;
|
||||||
|
error:
|
||||||
|
close(fdm);
|
||||||
|
PLOGE("pts_open");
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stores the previous termios of stdin
|
// Stores the previous termios of stdin
|
||||||
|
14
pts.h
14
pts.h
@ -1,17 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2013, Tan Chee Eng (@tan-ce)
|
* Copyright 2013, Tan Chee Eng (@tan-ce)
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -24,6 +12,8 @@
|
|||||||
#ifndef _PTS_H_
|
#ifndef _PTS_H_
|
||||||
#define _PTS_H_
|
#define _PTS_H_
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* pts_open
|
* pts_open
|
||||||
*
|
*
|
||||||
|
779
su.c
779
su.c
@ -1,209 +1,68 @@
|
|||||||
// vim: set ts=4 expandtab sw=4 :
|
|
||||||
/*
|
/*
|
||||||
** Copyright 2017, John Wu (@topjohnwu)
|
* Copyright 2017, John Wu (@topjohnwu)
|
||||||
** Copyright 2015, Pierre-Hugues Husson <phh@phh.me>
|
* Copyright 2015, Pierre-Hugues Husson <phh@phh.me>
|
||||||
** Copyright 2010, Adam Shanks (@ChainsDD)
|
* Copyright 2010, Adam Shanks (@ChainsDD)
|
||||||
** Copyright 2008, Zinx Verituse (@zinxv)
|
* 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.
|
/* su.c - The main function running in the daemon
|
||||||
** 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 _GNU_SOURCE
|
|
||||||
#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 <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include <strings.h>
|
#include <fcntl.h>
|
||||||
#include <stdint.h>
|
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
#include <sys/stat.h>
|
#include <errno.h>
|
||||||
#include <stdarg.h>
|
#include <signal.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <selinux/selinux.h>
|
#include <sys/stat.h>
|
||||||
#include <arpa/inet.h>
|
|
||||||
#include <sys/auxv.h>
|
#include <sys/auxv.h>
|
||||||
#include <sys/system_properties.h>
|
#include <selinux/selinux.h>
|
||||||
|
|
||||||
#include "su.h"
|
#include "magisk.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
#include "resetprop.h"
|
||||||
|
#include "su.h"
|
||||||
|
|
||||||
extern int is_daemon;
|
static struct su_context *su_ctx;
|
||||||
extern int daemon_from_uid;
|
|
||||||
extern int daemon_from_pid;
|
|
||||||
|
|
||||||
unsigned get_shell_uid() {
|
static void usage(int status) {
|
||||||
struct passwd* ppwd = getpwnam("shell");
|
FILE *stream = (status == EXIT_SUCCESS) ? stdout : stderr;
|
||||||
if (NULL == ppwd) {
|
|
||||||
return 2000;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ppwd->pw_uid;
|
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"
|
||||||
|
" -u display the multiuser mode and exit\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);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned get_system_uid() {
|
static char *concat_commands(int argc, char *argv[]) {
|
||||||
struct passwd* ppwd = getpwnam("system");
|
char command[ARG_MAX];
|
||||||
if (NULL == ppwd) {
|
command[0] = '\0';
|
||||||
return 1000;
|
for (int i = optind - 1; i < argc; ++i) {
|
||||||
|
if (strlen(command))
|
||||||
|
sprintf(command, "%s %s", command, argv[i]);
|
||||||
|
else
|
||||||
|
sprintf(command, "%s", argv[i]);
|
||||||
}
|
}
|
||||||
|
return strdup(command);
|
||||||
return ppwd->pw_uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned get_radio_uid() {
|
|
||||||
struct passwd* ppwd = getpwnam("radio");
|
|
||||||
if (NULL == ppwd) {
|
|
||||||
return 1001;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ppwd->pw_uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
int fork_zero_fucks() {
|
|
||||||
int pid = fork();
|
|
||||||
if (pid) {
|
|
||||||
int status;
|
|
||||||
waitpid(pid, &status, 0);
|
|
||||||
return pid;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if ( (pid = fork()) != 0)
|
|
||||||
exit(0);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
if (is_daemon) {
|
|
||||||
from->uid = daemon_from_uid;
|
|
||||||
from->pid = daemon_from_pid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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 int get_multiuser_mode() {
|
static int get_multiuser_mode() {
|
||||||
char *data;
|
// TODO: Multiuser support
|
||||||
char sdk_ver[PROPERTY_VALUE_MAX];
|
|
||||||
|
|
||||||
data = read_file("/system/build.prop");
|
|
||||||
get_property(data, sdk_ver, "ro.build.version.sdk", "0");
|
|
||||||
free(data);
|
|
||||||
|
|
||||||
int sdk = atoi(sdk_ver);
|
|
||||||
if (sdk < 17)
|
|
||||||
return MULTIUSER_MODE_NONE;
|
return MULTIUSER_MODE_NONE;
|
||||||
|
|
||||||
int ret = MULTIUSER_MODE_OWNER_ONLY;
|
|
||||||
char mode[12];
|
|
||||||
FILE *fp;
|
|
||||||
if ((fp = fopen(REQUESTOR_MULTIUSER_MODE, "r"))) {
|
|
||||||
fgets(mode, sizeof(mode), fp);
|
|
||||||
int last = strlen(mode) - 1;
|
|
||||||
if (mode[last] == '\n')
|
|
||||||
mode[last] = '\0';
|
|
||||||
if (strcmp(mode, MULTIUSER_VALUE_USER) == 0) {
|
|
||||||
ret = MULTIUSER_MODE_USER;
|
|
||||||
} else if (strcmp(mode, MULTIUSER_VALUE_OWNER_MANAGED) == 0) {
|
|
||||||
ret = MULTIUSER_MODE_OWNER_MANAGED;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ret = MULTIUSER_MODE_OWNER_ONLY;
|
|
||||||
}
|
|
||||||
fclose(fp);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void read_options(struct su_context *ctx) {
|
|
||||||
ctx->user.multiuser_mode = get_multiuser_mode();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void user_init(struct su_context *ctx) {
|
|
||||||
if (ctx->from.uid > 99999) {
|
|
||||||
ctx->user.android_user_id = ctx->from.uid / 100000;
|
|
||||||
if (ctx->user.multiuser_mode == MULTIUSER_MODE_USER) {
|
|
||||||
snprintf(ctx->user.database_path, PATH_MAX, "%s/%d/%s", REQUESTOR_USER_PATH, ctx->user.android_user_id, REQUESTOR_DATABASE_PATH);
|
|
||||||
snprintf(ctx->user.base_path, PATH_MAX, "%s/%d/%s", REQUESTOR_USER_PATH, ctx->user.android_user_id, REQUESTOR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void populate_environment(const struct su_context *ctx) {
|
static void populate_environment(const struct su_context *ctx) {
|
||||||
@ -226,355 +85,66 @@ static void populate_environment(const struct su_context *ctx) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_identity(unsigned int uid) {
|
static __attribute__ ((noreturn)) void allow() {
|
||||||
/*
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void socket_cleanup(struct su_context *ctx) {
|
|
||||||
if (ctx && ctx->sock_path[0]) {
|
|
||||||
if (unlink(ctx->sock_path))
|
|
||||||
PLOGE("unlink (%s)", ctx->sock_path);
|
|
||||||
ctx->sock_path[0] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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 void cleanup(void) {
|
|
||||||
socket_cleanup(su_ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void cleanup_signal(int sig) {
|
|
||||||
socket_cleanup(su_ctx);
|
|
||||||
exit(128 + sig);
|
|
||||||
}
|
|
||||||
|
|
||||||
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_LOCAL;
|
|
||||||
snprintf(path, len, "%s/.socket%d", REQUESTOR_CACHE_PATH, getpid());
|
|
||||||
memset(sun.sun_path, 0, sizeof(sun.sun_path));
|
|
||||||
snprintf(sun.sun_path, sizeof(sun.sun_path), "%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;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define write_data(fd, data, data_len) \
|
|
||||||
do { \
|
|
||||||
uint32_t __len = htonl(data_len); \
|
|
||||||
__len = write((fd), &__len, sizeof(__len)); \
|
|
||||||
if (__len != sizeof(__len)) { \
|
|
||||||
PLOGE("write(" #data ")"); \
|
|
||||||
return -1; \
|
|
||||||
} \
|
|
||||||
__len = write((fd), data, data_len); \
|
|
||||||
if (__len != data_len) { \
|
|
||||||
PLOGE("write(" #data ")"); \
|
|
||||||
return -1; \
|
|
||||||
} \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
#define write_string_data(fd, name, data) \
|
|
||||||
do { \
|
|
||||||
write_data(fd, name, strlen(name)); \
|
|
||||||
write_data(fd, data, strlen(data)); \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
// stringify everything.
|
|
||||||
#define write_token(fd, name, data) \
|
|
||||||
do { \
|
|
||||||
char buf[16]; \
|
|
||||||
snprintf(buf, sizeof(buf), "%d", data); \
|
|
||||||
write_string_data(fd, name, buf); \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
static int socket_send_request(int fd, const struct su_context *ctx) {
|
|
||||||
write_string_data(fd, "version", VERSION);
|
|
||||||
write_token(fd, "versionCode", VERSION_CODE);
|
|
||||||
write_token(fd, "uid", ctx->from.uid);
|
|
||||||
write_token(fd, "eof", PROTO_VERSION);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int socket_receive_result(int fd, char *result, ssize_t result_len) {
|
|
||||||
ssize_t len;
|
|
||||||
|
|
||||||
LOGD("waiting for user");
|
|
||||||
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"
|
|
||||||
" --daemon start the su daemon agent\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"
|
|
||||||
" -u display the multiuser mode and exit\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);
|
|
||||||
|
|
||||||
int send_to_app = 1;
|
|
||||||
|
|
||||||
// no need to log if called by root
|
|
||||||
if (ctx->from.uid == AID_ROOT)
|
|
||||||
send_to_app = 0;
|
|
||||||
|
|
||||||
// dumpstate (which logs to logcat/shell) will spam the crap out of the system with su calls
|
|
||||||
if (strcmp("/system/bin/dumpstate", ctx->from.bin) == 0)
|
|
||||||
send_to_app = 0;
|
|
||||||
|
|
||||||
if (send_to_app)
|
|
||||||
send_result(ctx, DENY);
|
|
||||||
|
|
||||||
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;
|
char *arg0;
|
||||||
int argc, err;
|
|
||||||
|
|
||||||
umask(ctx->umask);
|
umask(su_ctx->umask);
|
||||||
int send_to_app = 1;
|
|
||||||
|
|
||||||
// no need to log if called by root
|
// no need to log if called by root
|
||||||
if (ctx->from.uid == AID_ROOT)
|
if (su_ctx->from.uid != UID_ROOT)
|
||||||
send_to_app = 0;
|
app_send_result(su_ctx, ALLOW);
|
||||||
|
|
||||||
// dumpstate (which logs to logcat/shell) will spam the crap out of the system with su calls
|
char *binary = su_ctx->to.shell;
|
||||||
if (strcmp("/system/bin/dumpstate", ctx->from.bin) == 0)
|
if (su_ctx->to.command) {
|
||||||
send_to_app = 0;
|
su_ctx->to.argv[--su_ctx->to.argc] = su_ctx->to.command;
|
||||||
|
su_ctx->to.argv[--su_ctx->to.argc] = "-c";
|
||||||
if (send_to_app)
|
|
||||||
send_result(ctx, ALLOW);
|
|
||||||
|
|
||||||
char *binary;
|
|
||||||
argc = ctx->to.optind;
|
|
||||||
if (ctx->to.command) {
|
|
||||||
binary = ctx->to.shell;
|
|
||||||
ctx->to.argv[--argc] = ctx->to.command;
|
|
||||||
ctx->to.argv[--argc] = "-c";
|
|
||||||
}
|
|
||||||
else if (ctx->to.shell) {
|
|
||||||
binary = ctx->to.shell;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (ctx->to.argv[argc]) {
|
|
||||||
binary = ctx->to.argv[argc++];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
binary = DEFAULT_SHELL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
arg0 = strrchr (binary, '/');
|
arg0 = strrchr(binary, '/');
|
||||||
arg0 = (arg0) ? arg0 + 1 : binary;
|
arg0 = arg0 ? (arg0 + 1) : binary;
|
||||||
if (ctx->to.login) {
|
if (su_ctx->to.login) {
|
||||||
int s = strlen(arg0) + 2;
|
int s = strlen(arg0) + 2;
|
||||||
char *p = malloc(s);
|
char *p = xmalloc(s);
|
||||||
|
|
||||||
if (!p)
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
|
|
||||||
*p = '-';
|
*p = '-';
|
||||||
strcpy(p + 1, arg0);
|
strcpy(p + 1, arg0);
|
||||||
arg0 = p;
|
arg0 = p;
|
||||||
}
|
}
|
||||||
|
|
||||||
populate_environment(ctx);
|
populate_environment(su_ctx);
|
||||||
set_identity(ctx->to.uid);
|
set_identity(su_ctx->to.uid);
|
||||||
|
|
||||||
#define PARG(arg) \
|
|
||||||
(argc + (arg) < ctx->to.argc) ? " " : "", \
|
|
||||||
(argc + (arg) < ctx->to.argc) ? ctx->to.argv[argc + (arg)] : ""
|
|
||||||
|
|
||||||
LOGD("%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, ctx->from.bin,
|
|
||||||
ctx->to.uid, get_command(&ctx->to), binary,
|
|
||||||
arg0, PARG(0), PARG(1), PARG(2), PARG(3), PARG(4), PARG(5),
|
|
||||||
(ctx->to.optind + 6 < ctx->to.argc) ? " ..." : "");
|
|
||||||
|
|
||||||
setexeccon("u:r:su:s0");
|
setexeccon("u:r:su:s0");
|
||||||
|
|
||||||
ctx->to.argv[--argc] = arg0;
|
su_ctx->to.argv[--su_ctx->to.argc] = arg0;
|
||||||
execvp(binary, ctx->to.argv + argc);
|
execvp(binary, su_ctx->to.argv + su_ctx->to.argc);
|
||||||
err = errno;
|
fprintf(stderr, "Cannot execute %s: %s\n", binary, strerror(errno));
|
||||||
PLOGE("exec");
|
PLOGE("exec");
|
||||||
fprintf(stderr, "Cannot execute %s: %s\n", binary, strerror(err));
|
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int get_api_version() {
|
static __attribute__ ((noreturn)) void deny() {
|
||||||
char sdk_ver[PROPERTY_VALUE_MAX];
|
// no need to log if called by root
|
||||||
char *data = read_file("/system/build.prop");
|
if (su_ctx->from.uid != UID_ROOT)
|
||||||
get_property(data, sdk_ver, "ro.build.version.sdk", "0");
|
app_send_result(su_ctx, DENY);
|
||||||
int ver = atoi(sdk_ver);
|
|
||||||
free(data);
|
LOGW("su: request rejected (%u->%u)", su_ctx->from.uid, su_ctx->to.uid);
|
||||||
return ver;
|
fprintf(stderr, "%s\n", strerror(EACCES));
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void fork_for_samsung(void)
|
static void socket_cleanup() {
|
||||||
{
|
if (su_ctx && su_ctx->sock_path[0]) {
|
||||||
// Samsung CONFIG_SEC_RESTRICT_SETUID wants the parent process to have
|
unlink(su_ctx->sock_path);
|
||||||
// EUID 0, or else our setresuid() calls will be denied. So make sure
|
su_ctx->sock_path[0] = 0;
|
||||||
// all such syscalls are executed by a child process.
|
|
||||||
int rv;
|
|
||||||
|
|
||||||
switch (fork()) {
|
|
||||||
case 0:
|
|
||||||
return;
|
|
||||||
case -1:
|
|
||||||
PLOGE("fork");
|
|
||||||
exit(1);
|
|
||||||
default:
|
|
||||||
if (wait(&rv) < 0) {
|
|
||||||
exit(1);
|
|
||||||
} else {
|
|
||||||
exit(WEXITSTATUS(rv));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static char *concat_commands(int argc, char *argv[]) {
|
static void cleanup_signal(int sig) {
|
||||||
char command[ARG_MAX];
|
socket_cleanup();
|
||||||
int i;
|
exit(EXIT_FAILURE);
|
||||||
command[0] = '\0';
|
|
||||||
for (i = optind - 1; i < argc; ++i) {
|
|
||||||
if (strlen(command))
|
|
||||||
sprintf(command, "%s %s", command, argv[i]);
|
|
||||||
else
|
|
||||||
sprintf(command, "%s", argv[i]);
|
|
||||||
}
|
|
||||||
return strdup(command);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int su_main(int argc, char *argv[]) {
|
int su_daemon_main(int argc, char **argv) {
|
||||||
if (argc == 2 && strcmp(argv[1], "--daemon") == 0) {
|
|
||||||
//Everything we'll exec will be in su, not su_daemon
|
|
||||||
setexeccon("u:r:su:s0");
|
|
||||||
return run_daemon();
|
|
||||||
}
|
|
||||||
int ppid = getppid();
|
|
||||||
if ((geteuid() != AID_ROOT && getuid() != AID_ROOT) ||
|
|
||||||
(get_api_version() >= 18 && getuid() == AID_SHELL) ||
|
|
||||||
get_api_version() >= 19) {
|
|
||||||
// attempt to connect to daemon...
|
|
||||||
LOGD("starting daemon client %d %d", getuid(), geteuid());
|
|
||||||
return connect_daemon(argc, argv, ppid);
|
|
||||||
} else {
|
|
||||||
return su_main_nodaemon(argc, argv);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int su_main_nodaemon(int argc, char **argv) {
|
|
||||||
int ppid = getppid();
|
|
||||||
|
|
||||||
// Only fork when not running in daemon mode (already forked from daemon)
|
|
||||||
if (! is_daemon)
|
|
||||||
fork_for_samsung();
|
|
||||||
|
|
||||||
// Sanitize all secure environment variables (from linker_environ.c in AOSP linker).
|
// Sanitize all secure environment variables (from linker_environ.c in AOSP linker).
|
||||||
/* The same list than GLibc at this point */
|
/* The same list than GLibc at this point */
|
||||||
static const char* const unsec_vars[] = {
|
static const char* const unsec_vars[] = {
|
||||||
@ -615,8 +185,6 @@ int su_main_nodaemon(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGD("su invoked.");
|
|
||||||
|
|
||||||
// Replace -cn with z, for CF compatibility
|
// Replace -cn with z, for CF compatibility
|
||||||
for (int i = 0; i < argc; ++i) {
|
for (int i = 0; i < argc; ++i) {
|
||||||
if (strcmp(argv[i], "-cn") == 0) {
|
if (strcmp(argv[i], "-cn") == 0) {
|
||||||
@ -625,33 +193,34 @@ int su_main_nodaemon(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default values
|
||||||
struct su_context ctx = {
|
struct su_context ctx = {
|
||||||
.from = {
|
.from = {
|
||||||
.pid = -1,
|
.pid = from_pid,
|
||||||
.uid = 0,
|
.uid = from_uid,
|
||||||
.bin = "",
|
|
||||||
.args = "",
|
|
||||||
},
|
},
|
||||||
.to = {
|
.to = {
|
||||||
.uid = AID_ROOT,
|
.uid = UID_ROOT,
|
||||||
.login = 0,
|
.login = 0,
|
||||||
.keepenv = 0,
|
.keepenv = 0,
|
||||||
.shell = NULL,
|
.shell = DEFAULT_SHELL,
|
||||||
.command = NULL,
|
.command = NULL,
|
||||||
.argv = argv,
|
.argv = argv,
|
||||||
.argc = argc,
|
.argc = argc,
|
||||||
.optind = 0,
|
|
||||||
},
|
},
|
||||||
.user = {
|
.user = {
|
||||||
.android_user_id = 0,
|
.android_user_id = 0,
|
||||||
.multiuser_mode = MULTIUSER_MODE_OWNER_ONLY,
|
.multiuser_mode = get_multiuser_mode(),
|
||||||
.database_path = REQUESTOR_DATA_PATH REQUESTOR_DATABASE_PATH,
|
.database_path = APPLICATION_DATA_PATH REQUESTOR_DATABASE_PATH,
|
||||||
.base_path = REQUESTOR_DATA_PATH REQUESTOR
|
.base_path = APPLICATION_DATA_PATH REQUESTOR
|
||||||
},
|
},
|
||||||
|
.umask = umask(027),
|
||||||
};
|
};
|
||||||
|
su_ctx = &ctx;
|
||||||
|
|
||||||
struct stat st;
|
struct stat st;
|
||||||
int c, socket_serv_fd, fd;
|
int c, socket_serv_fd, fd;
|
||||||
char buf[64], *result;
|
char result[64];
|
||||||
policy_t dballow;
|
policy_t dballow;
|
||||||
struct option long_opts[] = {
|
struct option long_opts[] = {
|
||||||
{ "command", required_argument, NULL, 'c' },
|
{ "command", required_argument, NULL, 'c' },
|
||||||
@ -665,9 +234,8 @@ int su_main_nodaemon(int argc, char **argv) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
while ((c = getopt_long(argc, argv, "c:hlmps:Vvuz:", long_opts, NULL)) != -1) {
|
while ((c = getopt_long(argc, argv, "c:hlmps:Vvuz:", long_opts, NULL)) != -1) {
|
||||||
switch(c) {
|
switch (c) {
|
||||||
case 'c':
|
case 'c':
|
||||||
ctx.to.shell = DEFAULT_SHELL;
|
|
||||||
ctx.to.command = concat_commands(argc, argv);
|
ctx.to.command = concat_commands(argc, argv);
|
||||||
optind = argc;
|
optind = argc;
|
||||||
break;
|
break;
|
||||||
@ -688,10 +256,10 @@ int su_main_nodaemon(int argc, char **argv) {
|
|||||||
printf("%d\n", VERSION_CODE);
|
printf("%d\n", VERSION_CODE);
|
||||||
exit(EXIT_SUCCESS);
|
exit(EXIT_SUCCESS);
|
||||||
case 'v':
|
case 'v':
|
||||||
printf("%s\n", VERSION);
|
printf("%s\n", SU_VERSION_STR);
|
||||||
exit(EXIT_SUCCESS);
|
exit(EXIT_SUCCESS);
|
||||||
case 'u':
|
case 'u':
|
||||||
switch (get_multiuser_mode()) {
|
switch (ctx.user.multiuser_mode) {
|
||||||
case MULTIUSER_MODE_USER:
|
case MULTIUSER_MODE_USER:
|
||||||
printf("%s\n", MULTIUSER_VALUE_USER);
|
printf("%s\n", MULTIUSER_VALUE_USER);
|
||||||
break;
|
break;
|
||||||
@ -742,63 +310,20 @@ int su_main_nodaemon(int argc, char **argv) {
|
|||||||
if (optind < argc && !strcmp(argv[optind], "--")) {
|
if (optind < argc && !strcmp(argv[optind], "--")) {
|
||||||
optind++;
|
optind++;
|
||||||
}
|
}
|
||||||
ctx.to.optind = optind;
|
|
||||||
|
|
||||||
su_ctx = &ctx;
|
// Check property of root configuration
|
||||||
if (from_init(&ctx.from) < 0) {
|
char *root_prop = getprop(ROOT_ACCESS_PROP);
|
||||||
deny(&ctx);
|
if (root_prop) {
|
||||||
}
|
int prop_status = atoi(root_prop);
|
||||||
|
switch (prop_status) {
|
||||||
read_options(&ctx);
|
|
||||||
user_init(&ctx);
|
|
||||||
|
|
||||||
// the latter two are necessary for stock ROMs like note 2 which do dumb things with su, or crash otherwise
|
|
||||||
if (ctx.from.uid == AID_ROOT) {
|
|
||||||
LOGD("Allowing root/system/radio.");
|
|
||||||
allow(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify superuser is installed
|
|
||||||
if (stat(ctx.user.base_path, &st) < 0) {
|
|
||||||
// send to market (disabled, because people are and think this is hijacking their su)
|
|
||||||
// if (0 == strcmp(JAVA_PACKAGE_NAME, REQUESTOR))
|
|
||||||
// silent_run("am start -d http://www.clockworkmod.com/superuser/install.html -a android.intent.action.VIEW");
|
|
||||||
PLOGE("stat %s", ctx.user.base_path);
|
|
||||||
deny(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
// odd perms on superuser data dir
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// always allow if this is the superuser uid
|
|
||||||
// superuser needs to be able to reenable itself when disabled...
|
|
||||||
if (ctx.from.uid == st.st_uid) {
|
|
||||||
allow(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
// deny if this is a non owner request and owner mode only
|
|
||||||
if (ctx.user.multiuser_mode == MULTIUSER_MODE_OWNER_ONLY && ctx.user.android_user_id != 0) {
|
|
||||||
deny(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add prop check
|
|
||||||
char value[PROP_VALUE_MAX];
|
|
||||||
__system_property_get(ROOT_ACCESS_PROP, value);
|
|
||||||
if(strlen(value)) {
|
|
||||||
int prop_status = atoi(value);
|
|
||||||
switch(prop_status) {
|
|
||||||
case ROOT_ACCESS_DISABLED:
|
case ROOT_ACCESS_DISABLED:
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
case ROOT_ACCESS_APPS_ONLY:
|
case ROOT_ACCESS_APPS_ONLY:
|
||||||
if (ctx.from.uid == AID_SHELL)
|
if (ctx.from.uid == UID_SHELL)
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
break;
|
break;
|
||||||
case ROOT_ACCESS_ADB_ONLY:
|
case ROOT_ACCESS_ADB_ONLY:
|
||||||
if (ctx.from.uid != AID_SHELL)
|
if (ctx.from.uid != UID_SHELL)
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
break;
|
break;
|
||||||
case ROOT_ACCESS_APPS_AND_ADB:
|
case ROOT_ACCESS_APPS_AND_ADB:
|
||||||
@ -808,89 +333,99 @@ int su_main_nodaemon(int argc, char **argv) {
|
|||||||
} else {
|
} else {
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
free(root_prop);
|
||||||
|
|
||||||
ctx.umask = umask(027);
|
// The su_context setup is done, now every error leads to deny
|
||||||
|
err_handler = deny;
|
||||||
|
|
||||||
|
// It's in multiuser mode
|
||||||
|
if (ctx.from.uid > 99999) {
|
||||||
|
ctx.user.android_user_id = ctx.from.uid / 100000;
|
||||||
|
if (ctx.user.multiuser_mode == MULTIUSER_MODE_USER) {
|
||||||
|
snprintf(ctx.user.database_path, PATH_MAX, "%s/%d/%s",
|
||||||
|
USER_DATA_PATH, ctx.user.android_user_id, REQUESTOR_DATABASE_PATH);
|
||||||
|
snprintf(ctx.user.base_path, PATH_MAX, "%s/%d/%s",
|
||||||
|
USER_DATA_PATH, ctx.user.android_user_id, REQUESTOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow root to start root
|
||||||
|
if (ctx.from.uid == UID_ROOT) {
|
||||||
|
allow();
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify superuser is installed
|
||||||
|
xstat(ctx.user.base_path, &st);
|
||||||
|
|
||||||
|
// odd perms on superuser data dir
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
// always allow if this is the superuser uid
|
||||||
|
// superuser needs to be able to reenable itself when disabled...
|
||||||
|
if (ctx.from.uid == st.st_uid) {
|
||||||
|
allow();
|
||||||
|
}
|
||||||
|
|
||||||
|
// deny if this is a non owner request and owner mode only
|
||||||
|
if (ctx.user.multiuser_mode == MULTIUSER_MODE_OWNER_ONLY && ctx.user.android_user_id != 0) {
|
||||||
|
deny();
|
||||||
|
}
|
||||||
|
|
||||||
mkdir(REQUESTOR_CACHE_PATH, 0770);
|
mkdir(REQUESTOR_CACHE_PATH, 0770);
|
||||||
if (chown(REQUESTOR_CACHE_PATH, st.st_uid, st.st_gid)) {
|
if (chown(REQUESTOR_CACHE_PATH, st.st_uid, st.st_gid)) {
|
||||||
PLOGE("chown (%s, %u, %u)", REQUESTOR_CACHE_PATH, st.st_uid, st.st_gid);
|
PLOGE("chown (%s, %u, %u)", REQUESTOR_CACHE_PATH, st.st_uid, st.st_gid);
|
||||||
deny(&ctx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (setgroups(0, NULL)) {
|
if (setgroups(0, NULL)) {
|
||||||
PLOGE("setgroups");
|
PLOGE("setgroups");
|
||||||
deny(&ctx);
|
|
||||||
}
|
}
|
||||||
if (setegid(st.st_gid)) {
|
if (setegid(st.st_gid)) {
|
||||||
PLOGE("setegid (%u)", st.st_gid);
|
PLOGE("setegid (%u)", st.st_gid);
|
||||||
deny(&ctx);
|
|
||||||
}
|
}
|
||||||
if (seteuid(st.st_uid)) {
|
if (seteuid(st.st_uid)) {
|
||||||
PLOGE("seteuid (%u)", st.st_uid);
|
PLOGE("seteuid (%u)", st.st_uid);
|
||||||
deny(&ctx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If db exits, check directly instead query application
|
||||||
dballow = database_check(&ctx);
|
dballow = database_check(&ctx);
|
||||||
switch (dballow) {
|
switch (dballow) {
|
||||||
case INTERACTIVE:
|
case INTERACTIVE:
|
||||||
break;
|
break;
|
||||||
case ALLOW:
|
case ALLOW:
|
||||||
LOGD("db allowed");
|
allow();
|
||||||
allow(&ctx); /* never returns */
|
|
||||||
case DENY:
|
case DENY:
|
||||||
default:
|
default:
|
||||||
LOGD("db denied");
|
deny();
|
||||||
deny(&ctx); /* never returns too */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New request or no db exist, notify app for response
|
||||||
socket_serv_fd = socket_create_temp(ctx.sock_path, sizeof(ctx.sock_path));
|
socket_serv_fd = socket_create_temp(ctx.sock_path, sizeof(ctx.sock_path));
|
||||||
LOGD(ctx.sock_path);
|
setup_sighandlers(cleanup_signal);
|
||||||
if (socket_serv_fd < 0) {
|
|
||||||
deny(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
signal(SIGHUP, cleanup_signal);
|
// Start activity
|
||||||
signal(SIGPIPE, cleanup_signal);
|
app_send_request(su_ctx);
|
||||||
signal(SIGTERM, cleanup_signal);
|
|
||||||
signal(SIGQUIT, cleanup_signal);
|
|
||||||
signal(SIGINT, cleanup_signal);
|
|
||||||
signal(SIGABRT, cleanup_signal);
|
|
||||||
|
|
||||||
if (send_request(&ctx) < 0) {
|
atexit(socket_cleanup);
|
||||||
deny(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
atexit(cleanup);
|
|
||||||
|
|
||||||
fd = socket_accept(socket_serv_fd);
|
fd = socket_accept(socket_serv_fd);
|
||||||
if (fd < 0) {
|
socket_send_request(fd, &ctx);
|
||||||
deny(&ctx);
|
socket_receive_result(fd, result, sizeof(result));
|
||||||
}
|
|
||||||
if (socket_send_request(fd, &ctx)) {
|
|
||||||
deny(&ctx);
|
|
||||||
}
|
|
||||||
if (socket_receive_result(fd, buf, sizeof(buf))) {
|
|
||||||
deny(&ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
close(fd);
|
close(fd);
|
||||||
close(socket_serv_fd);
|
close(socket_serv_fd);
|
||||||
socket_cleanup(&ctx);
|
socket_cleanup();
|
||||||
|
|
||||||
result = buf;
|
if (strcmp(result, "socket:DENY") == 0) {
|
||||||
|
deny();
|
||||||
#define SOCKET_RESPONSE "socket:"
|
} else if (strcmp(result, "socket:ALLOW") == 0) {
|
||||||
if (strncmp(result, SOCKET_RESPONSE, sizeof(SOCKET_RESPONSE) - 1))
|
allow();
|
||||||
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 {
|
} else {
|
||||||
LOGE("unknown response from Superuser Requestor: %s", result);
|
LOGE("unknown response from Superuser Requestor: %s", result);
|
||||||
deny(&ctx);
|
deny();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
120
su.h
120
su.h
@ -1,41 +1,15 @@
|
|||||||
/*
|
/* su.h - Store all general su info
|
||||||
** 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
|
#ifndef _SU_H_
|
||||||
#define SU_h 1
|
#define _SU_H_
|
||||||
|
|
||||||
#include "magisk.h"
|
#include <limits.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
#ifndef AID_SHELL
|
#define SU_VERSION_STR xstr(VERSION) ":MAGISKSU (topjohnwu)"
|
||||||
#define AID_SHELL (get_shell_uid())
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef AID_ROOT
|
|
||||||
#define AID_ROOT 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef AID_SYSTEM
|
|
||||||
#define AID_SYSTEM (get_system_uid())
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef AID_RADIO
|
|
||||||
#define AID_RADIO (get_radio_uid())
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
// Property check for root access
|
||||||
#define ROOT_ACCESS_PROP "persist.sys.root_access"
|
#define ROOT_ACCESS_PROP "persist.sys.root_access"
|
||||||
#define ROOT_ACCESS_DISABLED 0
|
#define ROOT_ACCESS_DISABLED 0
|
||||||
#define ROOT_ACCESS_APPS_ONLY 1
|
#define ROOT_ACCESS_APPS_ONLY 1
|
||||||
@ -45,22 +19,19 @@
|
|||||||
// DO NOT CHANGE LINE BELOW, java package name will always be the same
|
// DO NOT CHANGE LINE BELOW, java package name will always be the same
|
||||||
#define JAVA_PACKAGE_NAME "com.topjohnwu.magisk"
|
#define JAVA_PACKAGE_NAME "com.topjohnwu.magisk"
|
||||||
|
|
||||||
|
#define APPLICATION_DATA_PATH "/data/data/"
|
||||||
|
#define USER_DATA_PATH "/data/user"
|
||||||
|
|
||||||
// If --rename-manifest-package is used in AAPT, this
|
// If --rename-manifest-package is used in AAPT, this
|
||||||
// must be changed to correspond to the new APK package name
|
// must be changed to correspond to the new APK package name
|
||||||
// See the two Android.mk files for more details.
|
// See the two Android.mk files for more details.
|
||||||
#ifndef REQUESTOR
|
|
||||||
#define REQUESTOR JAVA_PACKAGE_NAME
|
#define REQUESTOR JAVA_PACKAGE_NAME
|
||||||
#endif
|
|
||||||
// This is used if wrapping the fragment classes and activities
|
// This is used if wrapping the fragment classes and activities
|
||||||
// with classes in another package. CM requirement.
|
// with classes in another package.
|
||||||
#ifndef REQUESTOR_PREFIX
|
|
||||||
#define REQUESTOR_PREFIX JAVA_PACKAGE_NAME ".superuser"
|
#define REQUESTOR_PREFIX JAVA_PACKAGE_NAME ".superuser"
|
||||||
#endif
|
#define REQUESTOR_FILES_PATH APPLICATION_DATA_PATH REQUESTOR "/files"
|
||||||
#define REQUESTOR_DATA_PATH "/data/data/"
|
|
||||||
#define REQUESTOR_FILES_PATH REQUESTOR_DATA_PATH REQUESTOR "/files"
|
|
||||||
#define REQUESTOR_USER_PATH "/data/user/"
|
|
||||||
#define REQUESTOR_CACHE_PATH "/dev/" REQUESTOR
|
#define REQUESTOR_CACHE_PATH "/dev/" REQUESTOR
|
||||||
#define REQUESTOR_DAEMON_PATH "\0MAGISKSU"
|
|
||||||
// there's no guarantee that the db or files are actually created named as such by
|
// there's no guarantee that the db or files are actually created named as such by
|
||||||
// SQLiteOpenHelper, etc. Though that is the behavior as of current.
|
// SQLiteOpenHelper, etc. Though that is the behavior as of current.
|
||||||
// it is up to the Android application to symlink as appropriate.
|
// it is up to the Android application to symlink as appropriate.
|
||||||
@ -69,22 +40,9 @@
|
|||||||
|
|
||||||
#define DEFAULT_SHELL "/system/bin/sh"
|
#define DEFAULT_SHELL "/system/bin/sh"
|
||||||
|
|
||||||
#define xstr(a) str(a)
|
|
||||||
#define str(a) #a
|
|
||||||
|
|
||||||
#ifndef VERSION_CODE
|
|
||||||
#define VERSION_CODE 8
|
|
||||||
#endif
|
|
||||||
#define VERSION xstr(VERSION_CODE) ":MAGISKSU (topjohnwu)"
|
|
||||||
|
|
||||||
#define PROTO_VERSION 1
|
|
||||||
|
|
||||||
struct su_initiator {
|
struct su_initiator {
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
unsigned uid;
|
unsigned uid;
|
||||||
unsigned user;
|
|
||||||
char bin[PATH_MAX];
|
|
||||||
char args[4096];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct su_request {
|
struct su_request {
|
||||||
@ -95,7 +53,6 @@ struct su_request {
|
|||||||
char *command;
|
char *command;
|
||||||
char **argv;
|
char **argv;
|
||||||
int argc;
|
int argc;
|
||||||
int optind;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct su_user_info {
|
struct su_user_info {
|
||||||
@ -145,30 +102,33 @@ typedef enum {
|
|||||||
ALLOW = 2,
|
ALLOW = 2,
|
||||||
} policy_t;
|
} policy_t;
|
||||||
|
|
||||||
extern policy_t database_check(struct su_context *ctx);
|
extern int from_uid, from_pid;
|
||||||
extern void set_identity(unsigned int uid);
|
extern int quit_signals[];
|
||||||
extern int send_request(struct su_context *ctx);
|
|
||||||
extern int send_result(struct su_context *ctx, policy_t policy);
|
|
||||||
|
|
||||||
static inline char *get_command(const struct su_request *to)
|
// su.c
|
||||||
{
|
|
||||||
if (to->command)
|
|
||||||
return to->command;
|
|
||||||
if (to->shell)
|
|
||||||
return to->shell;
|
|
||||||
char* ret = to->argv[to->optind];
|
|
||||||
if (ret)
|
|
||||||
return ret;
|
|
||||||
return DEFAULT_SHELL;
|
|
||||||
}
|
|
||||||
|
|
||||||
int run_daemon();
|
int su_daemon_main(int argc, char **argv);
|
||||||
int connect_daemon(int argc, char *argv[], int ppid);
|
|
||||||
int su_main(int argc, char *argv[]);
|
// su_client.c
|
||||||
int su_main_nodaemon(int argc, char *argv[]);
|
|
||||||
// for when you give zero fucks about the state of the child process.
|
int socket_create_temp(char *path, size_t len);
|
||||||
// this version of fork understands you don't care about the child.
|
int socket_accept(int serv_fd);
|
||||||
// deadbeat dad fork.
|
void socket_send_request(int fd, const struct su_context *ctx);
|
||||||
int fork_zero_fucks();
|
void socket_receive_result(int fd, char *result, ssize_t result_len);
|
||||||
|
|
||||||
|
// activity.c
|
||||||
|
|
||||||
|
void app_send_result(struct su_context *ctx, policy_t policy);
|
||||||
|
void app_send_request(struct su_context *ctx);
|
||||||
|
|
||||||
|
// db.c
|
||||||
|
|
||||||
|
policy_t database_check(struct su_context *ctx);
|
||||||
|
|
||||||
|
// misc.c
|
||||||
|
|
||||||
|
void setup_sighandlers(void (*handler)(int));
|
||||||
|
void set_identity(unsigned uid);
|
||||||
|
char *get_command(const struct su_request *to);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
217
su_client.c
Normal file
217
su_client.c
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
/* su_client.c - The entrypoint for su, connect to daemon and send correct info
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
#include "magisk.h"
|
||||||
|
#include "daemon.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "su.h"
|
||||||
|
#include "pts.h"
|
||||||
|
|
||||||
|
int from_uid, from_pid;
|
||||||
|
|
||||||
|
struct su_daemon_info {
|
||||||
|
int child;
|
||||||
|
int client;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void *wait_result(void *args) {
|
||||||
|
struct su_daemon_info *info = args;
|
||||||
|
LOGD("su: wait_result waiting for %d\n", info->child);
|
||||||
|
err_handler = exit_thread;
|
||||||
|
int status, code;
|
||||||
|
|
||||||
|
if (waitpid(info->child, &status, 0) > 0)
|
||||||
|
code = WEXITSTATUS(status);
|
||||||
|
else
|
||||||
|
code = -1;
|
||||||
|
|
||||||
|
// Pass the return code back to the client
|
||||||
|
write_int(info->client, code);
|
||||||
|
LOGD("su: return code to client: %d\n", code);
|
||||||
|
close(info->client);
|
||||||
|
free(info);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sighandler(int sig) {
|
||||||
|
restore_stdin();
|
||||||
|
|
||||||
|
// Assume we'll only be called before death
|
||||||
|
// See note before sigaction() in set_stdin_raw()
|
||||||
|
//
|
||||||
|
// Now, close all standard I/O to cause the pumps
|
||||||
|
// to exit so we can continue and retrieve the exit
|
||||||
|
// code
|
||||||
|
close(STDIN_FILENO);
|
||||||
|
close(STDOUT_FILENO);
|
||||||
|
close(STDERR_FILENO);
|
||||||
|
|
||||||
|
// Put back all the default handlers
|
||||||
|
struct sigaction act;
|
||||||
|
|
||||||
|
memset(&act, 0, sizeof(act));
|
||||||
|
act.sa_handler = SIG_DFL;
|
||||||
|
for (int i = 0; quit_signals[i]; ++i) {
|
||||||
|
sigaction(quit_signals[i], &act, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void su_daemon_receiver(int client) {
|
||||||
|
LOGD("su: get request\n");
|
||||||
|
// Fork a new process, the child process will need to setsid,
|
||||||
|
// open a pseudo-terminal, and will eventually run exec
|
||||||
|
// The parent process (the root daemon) will open a new thread to
|
||||||
|
// send the return code back to our client
|
||||||
|
int child = fork();
|
||||||
|
if (child < 0) {
|
||||||
|
PLOGE("fork");
|
||||||
|
write(client, &child, sizeof(child));
|
||||||
|
close(client);
|
||||||
|
return;
|
||||||
|
} else if (child != 0) {
|
||||||
|
pthread_t wait_thread;
|
||||||
|
|
||||||
|
struct su_daemon_info *info = xmalloc(sizeof(*info));
|
||||||
|
info->client = client;
|
||||||
|
info->child = child;
|
||||||
|
|
||||||
|
// In parent, open a new thread to wait for the child to exit,
|
||||||
|
// and send the exit code across the wire.
|
||||||
|
xpthread_create(&wait_thread, NULL, wait_result, info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGD("su: child process started\n");
|
||||||
|
|
||||||
|
// ack
|
||||||
|
write_int(client, 1);
|
||||||
|
|
||||||
|
// Here we're in the child
|
||||||
|
// Set the error handler back to normal
|
||||||
|
err_handler = exit_proc;
|
||||||
|
// Become session leader
|
||||||
|
xsetsid();
|
||||||
|
|
||||||
|
// Check the credentials
|
||||||
|
struct ucred credentials;
|
||||||
|
socklen_t ucred_length = sizeof(struct ucred);
|
||||||
|
if(getsockopt(client, SOL_SOCKET, SO_PEERCRED, &credentials, &ucred_length))
|
||||||
|
PLOGE("getsockopt");
|
||||||
|
|
||||||
|
from_uid = credentials.uid;
|
||||||
|
from_pid = credentials.pid;
|
||||||
|
|
||||||
|
// Let's read some info from the socket
|
||||||
|
int argc = read_int(client);
|
||||||
|
if (argc < 0 || argc > 512) {
|
||||||
|
LOGE("unable to allocate args: %d", argc);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
LOGD("su: argc=[%d]\n", argc);
|
||||||
|
|
||||||
|
char **argv = (char**) xmalloc(sizeof(char*) * (argc + 1));
|
||||||
|
argv[argc] = NULL;
|
||||||
|
for (int i = 0; i < argc; i++) {
|
||||||
|
argv[i] = read_string(client);
|
||||||
|
LOGD("su: argv[%d]=[%s]\n", i, argv[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change directory to cwd
|
||||||
|
char *cwd = read_string(client);
|
||||||
|
LOGD("su: cwd=[%s]\n", cwd);
|
||||||
|
chdir(cwd);
|
||||||
|
free(cwd);
|
||||||
|
|
||||||
|
// Get pts_slave
|
||||||
|
char *pts_slave = read_string(client);
|
||||||
|
LOGD("su: pts_slave=[%s]\n", pts_slave);
|
||||||
|
|
||||||
|
// We no longer need the access to socket in the child, close it
|
||||||
|
close(client);
|
||||||
|
|
||||||
|
//Check pts_slave file is owned by daemon_from_uid
|
||||||
|
int ptsfd;
|
||||||
|
struct stat stbuf;
|
||||||
|
int res = xstat(pts_slave, &stbuf);
|
||||||
|
|
||||||
|
//If caller is not root, ensure the owner of pts_slave is the caller
|
||||||
|
if(stbuf.st_uid != credentials.uid && credentials.uid != 0) {
|
||||||
|
LOGE("Wrong permission of pts_slave");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opening the TTY has to occur after the
|
||||||
|
// fork() and setsid() so that it becomes
|
||||||
|
// our controlling TTY and not the daemon's
|
||||||
|
ptsfd = xopen(pts_slave, O_RDWR);
|
||||||
|
|
||||||
|
free(pts_slave);
|
||||||
|
|
||||||
|
// Swap out stdin, stdout, stderr
|
||||||
|
xdup2(ptsfd, STDIN_FILENO);
|
||||||
|
xdup2(ptsfd, STDOUT_FILENO);
|
||||||
|
xdup2(ptsfd, STDERR_FILENO);
|
||||||
|
|
||||||
|
close(ptsfd);
|
||||||
|
|
||||||
|
su_daemon_main(argc, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Connect daemon, send argc, argv, cwd, pts slave
|
||||||
|
*/
|
||||||
|
int su_client_main(int argc, char *argv[]) {
|
||||||
|
char buffer[PATH_MAX];
|
||||||
|
int ptmx, socketfd;
|
||||||
|
|
||||||
|
// Connect to client
|
||||||
|
socketfd = connect_daemon();
|
||||||
|
|
||||||
|
// Tell the daemon we are su
|
||||||
|
write_int(socketfd, SUPERUSER);
|
||||||
|
|
||||||
|
// Number of command line arguments
|
||||||
|
write_int(socketfd, argc);
|
||||||
|
|
||||||
|
// Command line arguments
|
||||||
|
for (int i = 0; i < argc; i++) {
|
||||||
|
write_string(socketfd, argv[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CWD
|
||||||
|
getcwd(buffer, sizeof(buffer));
|
||||||
|
write_string(socketfd, buffer);
|
||||||
|
|
||||||
|
// We need a PTY. Get one.
|
||||||
|
ptmx = pts_open(buffer, sizeof(buffer));
|
||||||
|
watch_sigwinch_async(STDOUT_FILENO, ptmx);
|
||||||
|
|
||||||
|
// Send the slave path to the daemon
|
||||||
|
write_string(socketfd, buffer);
|
||||||
|
|
||||||
|
// Wait for acknowledgement from daemon
|
||||||
|
read_int(socketfd);
|
||||||
|
|
||||||
|
setup_sighandlers(sighandler);
|
||||||
|
pump_stdin_async(ptmx);
|
||||||
|
pump_stdout_blocking(ptmx);
|
||||||
|
|
||||||
|
// Get the exit code
|
||||||
|
int code = read_int(socketfd);
|
||||||
|
close(socketfd);
|
||||||
|
|
||||||
|
return code;
|
||||||
|
}
|
101
su_socket.c
Normal file
101
su_socket.c
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/* su_socket.c - Functions for communication to client
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
|
||||||
|
#include "magisk.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "su.h"
|
||||||
|
|
||||||
|
int socket_create_temp(char *path, size_t len) {
|
||||||
|
int fd;
|
||||||
|
struct sockaddr_un sun;
|
||||||
|
|
||||||
|
fd = xsocket(AF_LOCAL, SOCK_STREAM, 0);
|
||||||
|
if (fcntl(fd, F_SETFD, FD_CLOEXEC)) {
|
||||||
|
PLOGE("fcntl FD_CLOEXEC");
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&sun, 0, sizeof(sun));
|
||||||
|
sun.sun_family = AF_LOCAL;
|
||||||
|
snprintf(path, len, "%s/.socket%d", REQUESTOR_CACHE_PATH, getpid());
|
||||||
|
snprintf(sun.sun_path, sizeof(sun.sun_path), "%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);
|
||||||
|
|
||||||
|
xbind(fd, (struct sockaddr*) &sun, sizeof(sun));
|
||||||
|
xlisten(fd, 1);
|
||||||
|
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
int socket_accept(int serv_fd) {
|
||||||
|
struct timeval tv;
|
||||||
|
fd_set fds;
|
||||||
|
int 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 xaccept(serv_fd, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define write_data(fd, data, data_len) \
|
||||||
|
do { \
|
||||||
|
uint32_t __len = htonl(data_len); \
|
||||||
|
__len = write((fd), &__len, sizeof(__len)); \
|
||||||
|
if (__len != sizeof(__len)) { \
|
||||||
|
PLOGE("write(" #data ")"); \
|
||||||
|
} \
|
||||||
|
__len = write((fd), data, data_len); \
|
||||||
|
if (__len != data_len) { \
|
||||||
|
PLOGE("write(" #data ")"); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define write_string_data(fd, name, data) \
|
||||||
|
do { \
|
||||||
|
write_data(fd, name, strlen(name)); \
|
||||||
|
write_data(fd, data, strlen(data)); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
// stringify everything.
|
||||||
|
#define write_token(fd, name, data) \
|
||||||
|
do { \
|
||||||
|
char buf[16]; \
|
||||||
|
snprintf(buf, sizeof(buf), "%d", data); \
|
||||||
|
write_string_data(fd, name, buf); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
void socket_send_request(int fd, const struct su_context *ctx) {
|
||||||
|
write_token(fd, "uid", ctx->from.uid);
|
||||||
|
write_token(fd, "eof", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void socket_receive_result(int fd, char *result, ssize_t result_len) {
|
||||||
|
ssize_t len;
|
||||||
|
len = xread(fd, result, result_len - 1);
|
||||||
|
result[len] = '\0';
|
||||||
|
}
|
112
utils.c
112
utils.c
@ -1,112 +0,0 @@
|
|||||||
/*
|
|
||||||
** 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;
|
|
||||||
}
|
|
30
utils.h
30
utils.h
@ -1,30 +0,0 @@
|
|||||||
/*
|
|
||||||
** 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
|
|
Loading…
Reference in New Issue
Block a user