aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTrumeet <yuuta@yuuta.moe>2022-12-23 15:02:44 -0800
committerTrumeet <yuuta@yuuta.moe>2022-12-23 15:02:44 -0800
commit6c02deb271622b4f14ad53a19aa26b1c194f43da (patch)
tree02d82877a544a14e0a20b9b32b17e2f78437826a
downloadsecdesk-6c02deb271622b4f14ad53a19aa26b1c194f43da.tar
secdesk-6c02deb271622b4f14ad53a19aa26b1c194f43da.tar.gz
secdesk-6c02deb271622b4f14ad53a19aa26b1c194f43da.tar.bz2
secdesk-6c02deb271622b4f14ad53a19aa26b1c194f43da.zip
First Commit
-rw-r--r--.gitignore77
-rw-r--r--.idea/.gitignore8
-rw-r--r--.idea/misc.xml4
-rw-r--r--.idea/modules.xml8
-rw-r--r--.idea/secdesk.iml2
-rw-r--r--.idea/vcs.xml6
-rw-r--r--CMakeLists.txt6
-rw-r--r--README.md127
-rw-r--r--common.h45
-rw-r--r--consent.c73
-rw-r--r--log.c45
-rw-r--r--log.h42
-rw-r--r--main.c54
-rw-r--r--sd.c205
14 files changed, 702 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3649d6d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,77 @@
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..79b3c94
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
+</project> \ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..2ca5aac
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/.idea/secdesk.iml" filepath="$PROJECT_DIR$/.idea/secdesk.iml" />
+ </modules>
+ </component>
+</project> \ No newline at end of file
diff --git a/.idea/secdesk.iml b/.idea/secdesk.iml
new file mode 100644
index 0000000..f08604b
--- /dev/null
+++ b/.idea/secdesk.iml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module classpath="CMake" type="CPP_MODULE" version="4" /> \ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
+ </component>
+</project> \ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..3a9a6f9
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,6 @@
+cmake_minimum_required(VERSION 3.24)
+project(secdesk C)
+
+set(CMAKE_C_STANDARD 11)
+
+add_executable(secdesk main.c log.c log.h common.h consent.c sd.c)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..dec9818
--- /dev/null
+++ b/README.md
@@ -0,0 +1,127 @@
+# SecDesk
+
+An attempt to port Windows *secure desktop* to Linux.
+
+## Background
+
+On Windows, every window belongs to a [desktop](https://learn.microsoft.com/en-us/windows/win32/winstation/desktops),
+and processes can (?) read the input of other windows, unless they are privileged (?).
+
+Winlogon and UAC consent protects user inputs (e.g. passwords) from being stolen by 1) running as `NT Authority\SYSTEM`
+, and running on the secure desktop, which is a separate desktop from the users'. This way, user processes cannot read
+inputs.
+
+macOS should (?) has a similar security measurement, but I am not sure.
+
+On Unix, X11 does not have such a protection, and user processes are free to read any other process's input. This
+includes your lock screen (e.g. `i3lock(1)`) or terminal (running `sudo(1)`).
+
+Wayland fixes this issue by implementing a security control, while X11 users are left unprotected.
+
+This proof-of-concept project aims at porting the secure desktop concept on Windows to Unix.
+
+## Threat model
+
+This project is aimed at preventing malware running as the unprivileged user to capture or hijack the input of
+sensitive dialogues. It assumes that the operating system (kernel, libraries, binaries, daemons, and everything
+privileged) is trusted.
+
+## Basic idea
+
+Without modifying the kernel, the easiest approach is to use virtual terminals. While most current Unix operating
+systems has virtual terminal support, this project is mainly focused on Linux due to the author's familiarity. Porting
+it to other Unix could be possible.
+
+Linux has multiple virtual terminals (defined in `MAX_NR_CONSOLES`), and each one can run a text terminal or GUI. The
+kernel provides text terminals: programs read and write to `/dev/ttyN`, and the kernel handles input and outputs from
+the keyboard and to the screen. Display servers can use framebuffer or DRM, as well as `/dev/input/`, to draw their own
+graphics.
+
+Popular implementations of display servers, like Xorg or wayland compositors using `seatd` are all using DRM and
+`/dev/input`.
+
+Each virtual terminal is independent of each other, and users may switch from one to another using `Ctrl + Alt + Fx` or
+`VT_ACTIVATE` ioctl.
+
+Each virtual terminal is isolated from each other. For the text terminals, `/dev/ttyN` has the default permission of
+`0620 root:tty`, and privileged display servers holding `/dev/input/*` does not pass keystrokes to user processes after
+switching to other virtual terminals (Xorg will keep these files open, while `seated` will close them).
+This is ideal for implementing a secure desktop on Linux.
+
+The approach is to start a separate display server (e.g. Xorg) on a free (unused) virtual terminal, switch to it, read
+sensitive data, switch back, and close the virtual terminal. This guarantees that unprivileged processes have no way to
+hijack the password dialogue, with the limitation that the password dialogue must be trusted and ran by root.
+For example, a dedicated X server coule be started using:
+
+```shell
+Xorg -background none :$NEW_DISPLAY vt$NEW_VT -nolisten tcp
+# Setup XAuth, so only root can connect.
+```
+
+Then, start a password dialogue:
+
+```shell
+DISPLAY=:$NEW_DISPLAY /usr/lib/ssh/x11-ssh-askpass
+```
+
+This would be the simplest form of a secure desktop.
+
+## Implementation
+
+To make the startup process faster, more portable, and simpler, I wrote this PoC that uses the text terminal instead of
+a display server. It is not a *desktop* in terms of the Windows secure desktop, but it satisfies the requirement of
+securely reading sensitive data.
+
+This PoC makes uses of various TTY and Console related ioctl (see `ioctl_console(2)` and `ioctl_tty(2)`), and it uses
+codes from [kbd](http://kbd-project.org/). How this PoC works is obvious:
+
+1. Open the TTY of the current process.
+2. Find an open virtual terminal (`VT_OPENQRY`) and open it.
+3. Make the virtual terminal the controlling terminal (`setsid(2)` and `TIOCSCTTY`).
+4. Read / write as usual using the file descriptor from step 2.
+5. Switch to it or switch back using `VT_ACTIVATE` and `VT_WAITACTIVATE`.
+
+## SAK
+
+Although the above process can safely read passwords, one more security measure must be taken into consideration: the
+user must know the authenticity of the password dialogue. That is, a user process may create a full-screen window to
+mimic the password dialogue in order to obtain the password.
+
+Windows mitigates this issue by having a SAK (Secure attention key), which is `Ctrl + Alt + Delete`. The NT kernel
+directly handles this key combo, and it notifies winlogon to show a privileged screen (either login screen or the
+security options page). Users can trust the page is authentic because no other programs shall capture the key combo.
+Moreover, domain administrators can enable the Require Ctrl Alt Del on Logon group policy to train users that they must
+press the SAK before login to ensure the authenticity of the login screen.
+
+However, this feature is missing on most current Unix operating systems. Linux is the only known Unix operating system
+to have a SAK, and it is less known and has little use. On Linux, the SAK is `SysRq + K`. The kernel will kill all
+processes (including the display server) in the current virtual terminal, so the service manager will restart the login
+prompt (either the display manger or getty), and it is authentic. This behaviour makes SAK on Linux very limited, as
+most users do not want their desktop programs to be killed just for logging in.
+
+This project took the advantage of the Linux SAK by forking a child to display the message `Press SysRq + K to continue`
+on the new virtual terminal and wait for it to be killed by `SIGKILL`. Although race condition could happen after
+`waitpid(2)` and `TIOCSCTTY`, it is still safe because the `/dev/ttyN` file does not allow non-root processes to write.
+
+## In the future
+
+This project is far from perfect: its authentication UI / UX is naive, and it requires running as root to open the TTY.
+In the future, I will make it an AskPass / PolKit agent, where unprivileged user can run use it to securely authenticate
+themselves. I will also fix the behaviour when running from environments like SSH (pts) or serial, where virtual
+terminals do not exist. In these environments, it should simply use the current terminal and inform the user that the
+terminal is insecure, which is what Windows RDP does regarding remote UAC consent.
+
+## Build and run
+
+```shell
+mkdir build
+cd build
+cmake ..
+sudo ./secdesktop password test # mode prompt
+```
+
+The code is ugly: it is written in 4 hours. I will try to make it pretty.
+
+## License
+
+WTFPL \ No newline at end of file
diff --git a/common.h b/common.h
new file mode 100644
index 0000000..0bf7c6d
--- /dev/null
+++ b/common.h
@@ -0,0 +1,45 @@
+/*
+ * Created by yuuta on 12/23/22.
+ */
+
+#ifndef SECDESK_COMMON_H
+#define SECDESK_COMMON_H
+
+#include <unistd.h>
+
+enum modes {
+ /* Yes / No on action <prompt>. */
+ mode_consent,
+ /* Enter your password for <prompt>.
+ * The program will have a copy of your password. */
+ mode_password,
+ /* Enter username and password for <prompt>.
+ * The program will neither know your password nor have access to your account.
+ * It just knows who you are. */
+ mode_auth
+};
+
+struct auth_env {
+ enum modes mode;
+ uid_t usr;
+ pid_t pid;
+ const char *prompt;
+};
+
+struct proc_env {
+ int in;
+ int out;
+ int err;
+};
+
+extern struct proc_env p_env;
+
+extern struct auth_env a_env;
+
+int sd_setup(void);
+
+void sd_cleanup(void);
+
+int main_consent(int secure);
+
+#endif /* SECDESK_COMMON_H */
diff --git a/consent.c b/consent.c
new file mode 100644
index 0000000..c2ab677
--- /dev/null
+++ b/consent.c
@@ -0,0 +1,73 @@
+/*
+ * Created by yuuta on 12/23/22.
+ */
+
+#include "common.h"
+#include "log.h"
+
+#include <stdio.h>
+#include <errno.h>
+
+int main_consent(int secure) {
+ if (!secure) {
+ printf("WARNING: The terminal you are about to enter passwords is insecure.\n");
+ } else {
+ printf("\033[r\033[H\033[J");
+ }
+ printf("Program %s (process %d: '%s'), is requesting your authorization.\n",
+ "/TODO",
+ a_env.pid,
+ "todo");
+ switch (a_env.mode) {
+ case mode_consent: {
+ printf("Do you consent '%s'? (Y / N) ", a_env.prompt);
+ char buf[5];
+ if (read(STDIN_FILENO, buf, sizeof(buf) - 1) < 0) {
+ int r = errno;
+ LOGFV("read: %m", r);
+ printf("Cannot read your response: %m. Press any key to return.\n",
+ errno);
+ fgetc(stdin);
+ if (secure) {
+ printf("\033[r\033[H\033[J");
+ }
+ return r;
+ }
+ if (buf[0] == 'Y' || buf[0] == 'y') {
+ dprintf(p_env.out, "1");
+ } else {
+ dprintf(p_env.out, "0");
+ }
+ break;
+ }
+ case mode_password: {
+ printf("Enter your password for '%s'. "
+ "The program will know your password.\n"
+ "Password: ", a_env.prompt);
+ char buf[10];
+ if (read(STDIN_FILENO, buf, sizeof(buf) - 1) < 0) {
+ int r = errno;
+ LOGFV("read: %m", r);
+ printf("Cannot read your response: %m. Press any key to return.\n",
+ errno);
+ fgetc(stdin);
+ if (secure) {
+ printf("\033[r\033[H\033[J");
+ }
+ return r;
+ }
+ buf[sizeof(buf) - 1] = '\0';
+ dprintf(p_env.out, "%s", buf);
+ break;
+ }
+ case mode_auth: {
+ // TODO
+ fgetc(stdin);
+ break;
+ }
+ }
+ if (secure) {
+ printf("\033[r\033[H\033[J");
+ }
+ return 0;
+}
diff --git a/log.c b/log.c
new file mode 100644
index 0000000..bdf7198
--- /dev/null
+++ b/log.c
@@ -0,0 +1,45 @@
+/*
+ * Created by yuuta on 1/1/22.
+ */
+
+#include "common.h"
+#include "log.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <assert.h>
+
+void g_log(enum log_level level,
+ const char *file,
+ int line,
+ const char *format,
+ ...) {
+ int stream = level <= log_warn || level == log_debug ? p_env.err : p_env.out;
+ switch (level) {
+ case log_fetal:
+ dprintf(stream, "F");
+ break;
+ case log_error:
+ dprintf(stream, "E");
+ break;
+ case log_warn:
+ dprintf(stream, "W");
+ break;
+ case log_info:
+ dprintf(stream, "I");
+ break;
+ case log_debug:
+ dprintf(stream, "D");
+ break;
+ default:
+ dprintf(p_env.err, "Unknown log level: %d.\n", level);
+ assert(0);
+ }
+ dprintf(stream, "[%s:%d]: ",
+ file, line);
+ va_list list;
+ va_start(list, format);
+ vdprintf(stream, format, list);
+ va_end(list);
+ dprintf(stream, "\n");
+}
diff --git a/log.h b/log.h
new file mode 100644
index 0000000..7d41fbc
--- /dev/null
+++ b/log.h
@@ -0,0 +1,42 @@
+/*
+ * Created by yuuta on 1/1/22.
+ */
+
+#ifndef LOG_H
+#define LOG_H
+
+enum log_level {
+ log_fetal = 1,
+ log_error = 2,
+ log_warn = 3,
+ log_info = 4,
+ log_debug = 5
+};
+
+void g_log(enum log_level level,
+ const char *file,
+ int line,
+ const char *format,
+ ...);
+
+#define LOGF(X) g_log(log_fetal, __FUNCTION__, __LINE__, X)
+
+#define LOGFV(X, ...) g_log(log_fetal, __FUNCTION__, __LINE__, X, __VA_ARGS__)
+
+#define LOGE(X) g_log(log_error, __FUNCTION__, __LINE__, X)
+
+#define LOGEV(X, ...) g_log(log_error, __FUNCTION__, __LINE__, X, __VA_ARGS__)
+
+#define LOGW(X) g_log(log_warn, __FUNCTION__, __LINE__, X)
+
+#define LOGWV(X, ...) g_log(log_warn, __FUNCTION__, __LINE__, X, __VA_ARGS__)
+
+#define LOGI(X) g_log(log_info, __FUNCTION__, __LINE__, X)
+
+#define LOGIV(X, ...) g_log(log_info, __FUNCTION__, __LINE__, X, __VA_ARGS__)
+
+#define LOGD(X) g_log(log_debug, __FUNCTION__, __LINE__, X)
+
+#define LOGDV(X, ...) g_log(log_debug, __FUNCTION__, __LINE__, X, __VA_ARGS__)
+
+#endif /* LOG_H */
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..4652583
--- /dev/null
+++ b/main.c
@@ -0,0 +1,54 @@
+#include "log.h"
+#include "common.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <signal.h>
+
+struct auth_env a_env;
+struct proc_env p_env = {
+ .in = STDIN_FILENO,
+ .out = STDOUT_FILENO,
+ .err = STDERR_FILENO
+};
+
+int main(int argc, char **argv) {
+ if (argc != 2 && argc != 3) {
+ fprintf(stderr, "Usage: %s consent|password|auth [prompt]\n",
+ argv[0]);
+ return 64;
+ }
+ if (!strcmp(argv[1], "consent")) {
+ a_env.mode = mode_consent;
+ } else if (!strcmp(argv[1], "password")) {
+ a_env.mode = mode_password;
+ } else if (!strcmp(argv[1], "auth")) {
+ a_env.mode = mode_auth;
+ } else {
+ fprintf(stderr, "Unknown mode: %s\n", argv[1]);
+ return 64;
+ }
+
+ if ((a_env.prompt = argv[2])) {
+ char *p = argv[2];
+ do {
+ if ((*p) < 32 || (*p) > 126) {
+ fprintf(stderr, "The given prompt is illegal.\n");
+ return 64;
+ }
+ } while (*(++ p));
+ }
+
+ /* Because we need to frequently dup(2). */
+ setbuf(stdout, NULL);
+ a_env.pid = getppid();
+ signal(SIGHUP, SIG_IGN);
+ if (sd_setup()) {
+ sd_cleanup();
+ return main_consent(0);
+ } else {
+ int r = main_consent(1);
+ sd_cleanup();
+ return r;
+ }
+}
diff --git a/sd.c b/sd.c
new file mode 100644
index 0000000..c987df3
--- /dev/null
+++ b/sd.c
@@ -0,0 +1,205 @@
+/*
+ * Created by yuuta on 12/23/22.
+ */
+
+#include "common.h"
+#include "log.h"
+
+#define IOCTL(a, b, ...) \
+if (ioctl(a, b, __VA_ARGS__) < 0) { LOGFV("ioctl(%d, 0x%x): %m", a, b, errno); return errno; }
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <linux/kd.h>
+#include <linux/vt.h>
+
+static const char *conspath[] = {
+ "/proc/self/fd/0",
+ "/proc/self/fd/1",
+ "/proc/self/fd/2",
+ "/dev/tty",
+ "/dev/tty0",
+ "/dev/vc/0",
+ "/dev/systty",
+ "/dev/console"
+};
+
+static int fd_con = -1;
+static struct vt_stat vts;
+static int vt_num = -1;
+static char vt_dev[PATH_MAX + 1];
+static int vt_fd = -1;
+static pid_t chld = -1;
+
+static int vt_switch(int to) {
+ if (to) {
+ IOCTL(fd_con, VT_ACTIVATE, vt_num)
+ IOCTL(fd_con, VT_WAITACTIVE, vt_num)
+ } else {
+ IOCTL(fd_con, VT_ACTIVATE, vts.v_active)
+ IOCTL(fd_con, VT_WAITACTIVE, vts.v_active)
+ }
+ return 0;
+}
+
+static int find_current_tty(void) {
+ for (unsigned int i = 0; i < sizeof(conspath) / sizeof(char *); i++) {
+ if ((fd_con = open(conspath[i], O_RDONLY)) == -1) {
+ LOGDV("Cannot open '%s': %m.",
+ conspath[i],
+ errno);
+ continue;
+ }
+ char arg;
+ errno = 0;
+ if (!isatty(fd_con) || ioctl(fd_con, KDGKBTYPE, &arg) ||
+ ((arg != KB_101) && (arg != KB_84))) {
+ if (errno) {
+ LOGDV("Cannot open '%s': %m.",
+ conspath[i]);
+ } else {
+ LOGDV("Cannot open '%s': Not a TTY.", conspath[i]);
+ }
+ close(fd_con);
+ fd_con = -1;
+ continue;
+ }
+ LOGDV("Opened '%s': %d.", conspath[i], fd_con);
+ break;
+ }
+ if (fd_con == -1) {
+ LOGE("Cannot get the current console. Try running as root to get tty0.");
+ return 1;
+ }
+ return 0;
+}
+
+static int main_sak(void) {
+ /* We have to take the ownership here - otherwise SAK will kill the parent. */
+ if (setsid() == -1) {
+ LOGFV("Cannot set session ID: %m.", errno);
+ return errno;
+ }
+ IOCTL(vt_fd, TIOCSCTTY, 0)
+ vt_switch(1);
+
+ dprintf(vt_fd, "\033[r\033[H\033[J"
+ "Press SysRq + K to continue.\n");
+ /* Wait for signal. */
+ while (1) {
+ pause();
+ }
+}
+
+int sd_setup(void) {
+ if (find_current_tty()) {
+ return 1;
+ }
+ IOCTL(fd_con, VT_GETSTATE, &vts)
+ IOCTL(fd_con, VT_OPENQRY, &vt_num)
+ snprintf(vt_dev, PATH_MAX, "/dev/tty%d", vt_num);
+ if ((vt_fd = open(vt_dev, O_RDWR)) == -1) {
+ LOGFV("Cannot open '%s': %m.", vt_dev, errno);
+ return errno;
+ }
+ LOGDV("Opened '%s': %d.", vt_dev, vt_fd);
+
+ if (!(chld = fork())) {
+ exit(main_sak());
+ }
+ if (chld == -1) {
+ LOGFV("Cannot fork: %m.", errno);
+ return errno;
+ }
+ int chld_status = -1;
+ if (waitpid(chld, &chld_status, 0) != chld) {
+ LOGFV("Cannot wait for child: %m.", errno);
+ return errno;
+ }
+ /* It seems like we need to open(2) again, or TIOCSCTTY will fail with EIO. */
+ close(vt_fd);
+ vt_fd = -1;
+
+ if (!WIFSIGNALED(chld_status)) {
+ LOGF("Not killed by signal. Considered insecure.");
+ return 1;
+ }
+ switch (WTERMSIG(chld_status)) {
+ case SIGKILL: {
+ break;
+ }
+ case SIGINT:
+ case SIGTERM: {
+ exit(13);
+ }
+ default: {
+ /* Consider insecure.
+ * Switch back and use the insecure view. */
+ LOGF("Killed by an incorrect signal. Considered insecure.");
+ return 1;
+ }
+ }
+
+ /* Take ownership */
+ if ((vt_fd = open(vt_dev, O_RDWR)) == -1) {
+ LOGFV("Cannot open '%s': %m.", vt_dev, errno);
+ return errno;
+ }
+ LOGDV("Opened '%s': %d.", vt_dev, vt_fd);
+ if (setsid() == -1) {
+ LOGFV("Cannot set session ID: %m.", errno);
+ return errno;
+ }
+ IOCTL(vt_fd, TIOCSCTTY, 1)
+
+ int in_bak, out_bak, err_bak;
+ if ((in_bak = dup(p_env.in)) == -1 ||
+ (out_bak = dup(p_env.out)) == -1 ||
+ (err_bak = dup(p_env.err)) == -1) {
+ LOGFV("dup: %m", errno);
+ return errno;
+ }
+
+ p_env.in = in_bak;
+ p_env.out = out_bak;
+ p_env.err = err_bak;
+
+ if (dup2(vt_fd, STDIN_FILENO) == -1 ||
+ dup2(vt_fd, STDOUT_FILENO) == -1 ||
+ dup2(vt_fd, STDERR_FILENO) == -1) {
+ LOGFV("dup2: %m", errno);
+ }
+
+ return 0;
+}
+
+void sd_cleanup(void) {
+ if (vt_fd >= 0) {
+ if (ioctl(vt_fd, TIOCNOTTY) < 0) {
+ LOGEV("Cannot deallocate console: %m", errno);
+ }
+ if (p_env.in != STDIN_FILENO) p_env.in = dup2(p_env.in, STDIN_FILENO);
+ if (p_env.out != STDOUT_FILENO) p_env.out = dup2(p_env.out, STDOUT_FILENO);
+ if (p_env.err != STDERR_FILENO) p_env.err = dup2(p_env.err, STDERR_FILENO);
+ /* It should already be closed? */
+ close(vt_fd);
+ vt_fd = -1;
+ }
+ if (fd_con >= 0) {
+ if (vt_switch(0)) {
+ LOGEV("Cannot return to the previous console: %m", errno);
+ }
+ if (ioctl(fd_con, VT_DISALLOCATE, vt_num) < 0) {
+ LOGEV("Cannot deallocate console: %m", errno);
+ }
+ close(fd_con);
+ fd_con = -1;
+ }
+}