我已经构建了一个通过以下方式模拟 HID 设备的应用程序/dev/uhid
在Linux上。我的应用程序分为两个程序。首先,一个非常简单的 setuid root 二进制文件,它/dev/uhid
仅打开并模拟一个设备,将消息来回传递给调用它的程序。其次,一个应用程序实际上包含所有设备逻辑,并使用其他二进制文件来封装uhid_event
消息并与内核通信。
任何具有控制台访问权限的人都可以插入硬件 USB 设备,但为了安全起见,我希望 setuid 程序拒绝代表非控制台用户运行。
我的问题:setuid root 应用程序检查它是否由控制台用户调用并在没有调用时进行保释,或者首先将程序的执行限制为控制台用户,最简单、最可靠的方法是什么?
答案1
您所说的控制台是指硬件控制台、系统控制台还是任何 VT?如果您的意思是后者,一个真正简单的解决方案是测试 STD*_FILENO 是否与每个 tty 关联,并调用 isatty() 来查看是否有任何流与 VT 关联。如果是,则检查 PID 是否等于进程组 ID。如果其中一个标准流与 tty 关联,并且当前进程是进程组领导者,则该程序很可能是由用户在某种 VT 上运行的。
编辑1:进一步澄清,最初的问题是关于本地/远程登录而不是虚拟终端与其他所有问题,上面的答案是没有意义的。
据我所知,用户会计数据库 UTMP/UTMPX API 是唯一提到远程登录的数据库,因此这可能是最好的解决方案。只需搜索用户记帐数据库 ut_host 字段即可查看有效的 IP 地址是否与用户登录相关联。
答案2
所以我不知道这是否安全,但从表面上看它似乎给了我我想要的东西。必须与 联系起来-lsystemd
。希望有人对安全性发表评论或发布更好的答案......
#include <cstring>
#include <iostream>
#include <stdlib.h>
#include <unistd.h>
#include <systemd/sd-login.h>
int
is_remote()
{
char *s = NULL;
int n = sd_pid_get_session(getpid(), &s);
if (n < 0) {
std::cerr << "sd_pid_get_session: " << std::strerror(-n) << std::endl;
return -1;
}
n = sd_session_is_remote(s);
free(s);
if (n < 0) {
std::cerr << "sd_pid_get_session: " << std::strerror(-n) << std::endl;
return -1;
}
return n;
}
int
main(int argc, char **argv)
{
if (is_remote()) {
std::cerr << "remote access not allowed" << std::endl;
return 1;
}
// Do actual program ...
return 0;
}
答案3
如果您使用 C 或 C++,并且不需要 systemd 依赖项,则可以使用 POSIX 标准ttyname()
或者ttyname_r()
获取进程的控制终端:
概要
#include <unistd.h> char *ttyname(int fildes); int ttyname_r(int fildes, char *name, size_t namesize);
描述
该
ttyname()
函数应返回一个指向字符串的指针,该字符串包含与文件描述符关联的终端的以空结尾的路径名fildes
。应用程序不得修改返回的字符串。返回的指针可能会失效,或者字符串内容可能会被后续调用覆盖ttyname()
。如果调用线程终止,返回的指针和字符串内容也可能无效。该
ttyname()
函数不必是线程安全的。该
ttyname_r()
函数应将与文件描述符关联的终端的以空结尾的路径名存储fildes
在 引用的字符数组中name
。该数组的namesize
长度为字符,并且应该为名称和终止空字符留出空间。终端名称的最大长度应为{TTY_NAME_MAX}。
只需通过0
(或STDIN_FILENO
) 以可移植的方式获取进程控制终端的名称。
在我检查过的每个 Linux 实例上,登录到文本控制台的用户都有一个/dev/ttyN
tty。
这对于文本控制台登录来说似乎是确定的。
图形登录有点困难。在 Linux 上,您将获得一个伪终端名称,例如/dev/pts/N
.这意味着您的DISPLAY
环境变量是您的控制终端。作为第一个近似值,如果是:0
或:0.0
,则该进程几乎肯定是由登录到物理控制台的某人运行的。这对您来说可能就足够了,尽管您可以使用环境变量XOpenDisplay()
中的值进行调用DISPLAY
来确定。
这将错误地识别任何使用 XVNC 或其他远程桌面协议来访问配置为向这些用户提供:0
显示的系统的人,而不是从:10
登录到物理控制台的显示开始。这是我从未见过的事情,但理论上是可能的。
如果您不必处理这个问题,那么您现在已经确定您的进程正在 Linux 物理控制台上运行。
如果您确实必须处理这种情况,则必须确定您正在连接的 X 服务器是否正在物理控制台上运行。顺便说一句,我不知道有什么简单的方法可以做到这一点,但是如果它是本地显示器,您应该能够从中获取 PID lsof /tmp/.X11-unix/XN
(尽管解析 的输出fuser
可能更容易),环境变量N
中的显示编号在哪里DISPLAY
。一旦获得 PID,您就可以读取/proc/PID/fd/0
以获取 X 服务器的控制终端,如果是/dev/ttyN
,则再次表明用户已登录到物理控制台,可以对系统进行物理访问。