* Qingtian Init Process for Enclaves
*
* Copyright (c) Huawei Technologies Co., Ltd. 2022. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*
* Lots of code copied from AWS nitro enclave bootstrap, and modified to
* be useful in qingtian_enclaves:
* https://github.com/aws/aws-nitro-enclaves-sdk-bootstrap/blob/main/init/init.c
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Code from OpenGCS (https://github.com/microsoft/opengcs) is used in this file.
* Copyright (c) Microsoft Corporation. All rights reserved.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <sys/reboot.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <linux/vm_sockets.h>
#include <unistd.h>
#include <fcntl.h>
struct mount_param {
const char *source;
const char *target;
const char *filesystemtype;
unsigned long mountflags;
const void *data;
};
struct symlink_param {
const char *linkpath;
const char *target;
};
struct mkdir_param {
const char *path;
mode_t mode;
};
struct fs_op {
void (*fn)(struct fs_op *param);
int ignore_errval;
union {
struct mount_param mount;
struct symlink_param symlink;
struct mkdir_param mkdir;
} u;
};
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#define HEART_BEAT 0xB7
#define VSOCK_PORT 9000
#define VSOCK_CID 3
#define warn(fmt, ...) \
fprintf(stderr, "qtinit: warn: %s:%d " fmt, \
__func__, __LINE__, ##__VA_ARGS__)
#define info(fmt, ...) \
fprintf(stdout, "qtinit: info: %s:%d " fmt, \
__func__, __LINE__, ##__VA_ARGS__)
#define term_cond(cond, fmt, ...) \
do { \
if ((cond)) { \
fprintf(stderr, "qtinit: err: %s:%d " fmt, \
__func__, __LINE__, ##__VA_ARGS__); \
exit(errno); \
} \
} while (0)
#define QTSM_PATH "/qtsm.ko"
#define DEFAULT_ENV_PATH "PATH=/sbin:/usr/sbin:/bin:/usr/bin"
#define MNT_DEFFLAGS (MS_NOSUID | MS_NOEXEC | MS_NODEV)
#define DEV_FLAGS (MS_NOSUID | MS_NOEXEC)
#define FILE_RIGHTS 0755
static void qtinit_block_signals(bool block)
{
sigset_t set;
sigfillset(&set);
sigprocmask(block ? SIG_BLOCK : SIG_UNBLOCK, &set, 0);
}
static inline int finit_module(int fd, const char *param_values, int flags)
{
return (int)syscall(__NR_finit_module, fd, param_values, flags);
}
static void qtinit_mount(struct fs_op *p)
{
struct mount_param *mp = &p->u.mount;
errno = 0;
if (mount(mp->source, mp->target, mp->filesystemtype, mp->mountflags, mp->data) < 0)
term_cond(errno != p->ignore_errval,
"mount %s failed, %s\n", mp->target, strerror(errno));
info("mount %s success\n", mp->target);
}
static void qtinit_symlink(struct fs_op *p)
{
struct symlink_param *sp = &p->u.symlink;
errno = 0;
if (symlink(sp->target, sp->linkpath) < 0)
term_cond(errno != p->ignore_errval,
"symlink %s to %s failed, %s\n",
sp->linkpath, sp->target, strerror(errno));
info("symlink %s to %s success\n", sp->linkpath, sp->target);
}
static void qtinit_mkdir(struct fs_op *p)
{
struct mkdir_param *mp = &p->u.mkdir;
errno = 0;
if (mkdir(mp->path, mp->mode) < 0)
term_cond(errno != p->ignore_errval,
"mkdir %s(%3o) failed, %s\n",
mp->path, mp->mode & 0777, strerror(errno));
info("mkdir %s(%3o) success\n", mp->path, mp->mode & 0777);
}
static struct fs_op qtinit_fs[] = {
{qtinit_mount, EBUSY, .u.mount = {"dev", "/dev", "devtmpfs", DEV_FLAGS, NULL}},
{qtinit_mount, EEXIST, .u.mount = {"proc", "/proc", "proc", MNT_DEFFLAGS, NULL}},
{qtinit_symlink, EEXIST, .u.symlink = {"/dev/fd", "/proc/self/fd"}},
{qtinit_symlink, EEXIST, .u.symlink = {"/dev/stdin", "/proc/self/fd/0"}},
{qtinit_symlink, EEXIST, .u.symlink = {"/dev/stdout", "/proc/self/fd/1"}},
{qtinit_symlink, EEXIST, .u.symlink = {"/dev/stderr", "/proc/self/fd/2"}},
{qtinit_mount, EEXIST, .u.mount = {"tmpfs", "/tmp", "tmpfs", MNT_DEFFLAGS, NULL}},
{qtinit_mount, EEXIST, .u.mount = {"tmpfs", "/run", "tmpfs", MNT_DEFFLAGS, "mode=0755"}},
{qtinit_mkdir, EEXIST, .u.mkdir = {"/dev/shm", FILE_RIGHTS}},
{qtinit_mount, EEXIST, .u.mount = {"shm", "/dev/shm", "tmpfs", MNT_DEFFLAGS, NULL}},
{qtinit_mkdir, EEXIST, .u.mkdir = {"/dev/pts", FILE_RIGHTS}},
{qtinit_mount, EEXIST, .u.mount = {"devpts", "/dev/pts", "devpts", MNT_DEFFLAGS, NULL}},
{qtinit_mount, EEXIST, .u.mount = {"sysfs", "/sys", "sysfs", MNT_DEFFLAGS, NULL}},
{qtinit_mount, EEXIST, .u.mount = {"cgroup_root", "/sys/fs/cgroup", "tmpfs", MNT_DEFFLAGS, "mode=0755"}},
};
static void qtinit_prepare_fs(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(qtinit_fs); i++)
qtinit_fs[i].fn(&qtinit_fs[i]);
info("Prepare fs success\n");
}
static void qtinit_prepare_cgroups(void)
{
char target[128];
char line[128];
char name[64];
int hierarchy, groups, enabled;
FILE *fp;
fp = fopen("/proc/cgroups", "r");
term_cond(!fp, "open /proc/cgroups failed, %s\n", strerror(errno));
fgets(line, sizeof(line), fp);
term_cond(ferror(fp), "read /proc/cgroups failed, %s\n", strerror(errno));
while (fgets(line, sizeof(line), fp) != NULL) {
memset(name, 0, sizeof(name));
term_cond(sscanf(line, "%63s %d %d %d",
name, &hierarchy, &groups, &enabled) != 4,
"parse '%s' failed, %s\n", line, strerror(errno));
if (!enabled)
continue;
sprintf(target, "/sys/fs/cgroup/%s", name);
term_cond(mkdir(target, FILE_RIGHTS) < 0,
"mkdir %s failed, %s\n", target, strerror(errno));
term_cond(mount(name, target, "cgroup", MNT_DEFFLAGS, name) < 0,
"mount %s failed, %s\n", target, strerror(errno));
}
term_cond(ferror(fp), "read /proc/cgroups failed, %s\n", strerror(errno));
fclose(fp);
info("Prepare cgroups success\n");
}
static void qtinit_prepare_console(void)
{
const char *console = "/dev/console";
term_cond(!freopen(console, "r", stdin),
"reopen %s failed, %s\n", console, strerror(errno));
term_cond(!freopen(console, "w", stdout),
"reopen %s failed, %s\n", console, strerror(errno));
term_cond(!freopen(console, "w", stderr),
"reopen %s failed, %s\n", console, strerror(errno));
info("Prepare console success\n");
}
static void qtinit_install_qtsm(void)
{
int fd;
fd = open(QTSM_PATH, O_RDONLY | O_CLOEXEC);
term_cond(fd < 0, "open %s failed, %s\n", QTSM_PATH, strerror(errno));
term_cond(finit_module(fd, "", 0) < 0,
"insmod %s failed, %s\n", QTSM_PATH, strerror(errno));
close(fd);
info("Install %s success\n", QTSM_PATH);
}
static void qtinit_enclave_ready(void)
{
int socket_fd;
struct sockaddr_vm sa;
unsigned char buf[1];
memset(&sa, 0, sizeof(sa));
sa.svm_family = AF_VSOCK;
sa.svm_cid = VSOCK_CID;
sa.svm_port = VSOCK_PORT;
buf[0] = HEART_BEAT;
socket_fd = socket(AF_VSOCK, SOCK_STREAM, 0);
term_cond(socket_fd < 0, "socket failed, %s\n", strerror(errno));
term_cond(connect(socket_fd, (struct sockaddr*)&sa, sizeof(sa)) < 0,
"connect failed, %s\n", strerror(errno));
term_cond(write(socket_fd, buf, 1) != 1,
"write failed, %s\n", strerror(errno));
term_cond(read(socket_fd, buf, 1) != 1,
"read failed, %s\n", strerror(errno));
term_cond(buf[0] != HEART_BEAT,
"received wrong message, %s\n", strerror(errno));
close(socket_fd);
info("Heartbeat success\n");
}
static char **qtinit_read_config(const char *file)
{
char line[512];
char **s = NULL;
int cnt;
FILE *fp;
fp = fopen(file, "r");
term_cond(!fp, "open %s failed, %s\n", file, strerror(errno));
cnt = 0;
while (fgets(line, sizeof(line), fp) != NULL) {
size_t len = strlen(line);
if (line[len - 1] == '\n')
line[len - 1] = 0;
cnt++;
s = realloc(s, cnt * sizeof(char *));
term_cond(!s, "parse config failed\n");
s[cnt - 1] = strdup(line);
term_cond(!s[cnt-1], "parse config failed\n");
}
term_cond(ferror(fp), "read %s failed, %s\n", file, strerror(errno));
if (cnt > 0) {
s = realloc(s, (cnt + 1) * sizeof(char *));
term_cond(!s, "parse config failed\n");
s[cnt] = NULL;
}
fclose(fp);
return s;
}
static const char *const default_env[] = {
DEFAULT_ENV_PATH,
NULL,
};
static const char *const default_cmd[] = {
"sh",
NULL,
};
static pid_t qtinit_start_app(void)
{
char **cmd, **env;
pid_t pid;
cmd = qtinit_read_config("/cmd");
if (!cmd) {
warn("cmd is empty, using default cmd\n");
cmd = (char **)default_cmd;
}
env = qtinit_read_config("/env");
if (!env) {
warn("env is empty, using default env\n");
env = (char **)default_env;
}
pid = fork();
if (pid != 0) {
term_cond(pid < 0, "fork failed, %s\n", strerror(errno));
return pid;
}
qtinit_block_signals(false);
setsid();
setpgid(0, 0);
info("Begin to run '%s'\n", cmd[0]);
term_cond(putenv(DEFAULT_ENV_PATH),
"putenv '%s' failed, %s\n", DEFAULT_ENV_PATH, strerror(errno));
execvpe(cmd[0], cmd, env);
term_cond(true, "Should never reach here.\n");
return pid;
}
static void qtinit_wait_app(pid_t pid)
{
int status;
term_cond(waitpid(pid, &status, 0) == -1,
"waitpid failed, %s\n", strerror(errno));
if (WIFEXITED(status))
info("pid %ld exited with %d\n", pid, WEXITSTATUS(status));
else if (WIFSIGNALED(status))
info("pid %ld exited with signal %d\n", pid, WTERMSIG(status));
else
info("pid %ld exited with status=0x%x\n", pid, status);
}
int main()
{
pid_t pid;
qtinit_block_signals(true);
qtinit_fs[0].fn(&qtinit_fs[0]);
qtinit_prepare_console();
qtinit_prepare_fs();
qtinit_prepare_cgroups();
qtinit_install_qtsm();
qtinit_enclave_ready();
pid = qtinit_start_app();
qtinit_wait_app(pid);
reboot(RB_POWER_OFF);
return 0;
}