diff options
author | Trumeet <yuuta@yuuta.moe> | 2022-12-23 15:02:44 -0800 |
---|---|---|
committer | Trumeet <yuuta@yuuta.moe> | 2022-12-23 15:02:44 -0800 |
commit | 6c02deb271622b4f14ad53a19aa26b1c194f43da (patch) | |
tree | 02d82877a544a14e0a20b9b32b17e2f78437826a | |
download | secdesk-6c02deb271622b4f14ad53a19aa26b1c194f43da.tar secdesk-6c02deb271622b4f14ad53a19aa26b1c194f43da.tar.gz secdesk-6c02deb271622b4f14ad53a19aa26b1c194f43da.tar.bz2 secdesk-6c02deb271622b4f14ad53a19aa26b1c194f43da.zip |
First Commit
-rw-r--r-- | .gitignore | 77 | ||||
-rw-r--r-- | .idea/.gitignore | 8 | ||||
-rw-r--r-- | .idea/misc.xml | 4 | ||||
-rw-r--r-- | .idea/modules.xml | 8 | ||||
-rw-r--r-- | .idea/secdesk.iml | 2 | ||||
-rw-r--r-- | .idea/vcs.xml | 6 | ||||
-rw-r--r-- | CMakeLists.txt | 6 | ||||
-rw-r--r-- | README.md | 127 | ||||
-rw-r--r-- | common.h | 45 | ||||
-rw-r--r-- | consent.c | 73 | ||||
-rw-r--r-- | log.c | 45 | ||||
-rw-r--r-- | log.h | 42 | ||||
-rw-r--r-- | main.c | 54 | ||||
-rw-r--r-- | sd.c | 205 |
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; +} @@ -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"); +} @@ -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 */ @@ -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; + } +} @@ -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; + } +} |