更广泛的背景下,我使用 QEMU+KVM 来虚拟化 ubuntu 机器,如下所示:
qemu-system-x86_64 \
-enable-kvm \
-m 2048 \
-nic none \
-vnc :5 \
-hda test-vm.raw \
-device vfio-pci,host=01:00.0 \
-serial unix:/tmp/console.sock,server,nowait
我用来/tmp/console.sock
通过“串行”登录虚拟机:
socat stdin,raw,echo=0,escape=0x11 "unix-connect:/tmp/console.sock"
这可以正常工作,不会出现任何重大问题。
但是,当我输入密码时,sudo su
启动该agetty
进程的终端(而不是串行控制台)上会出现输入密码的提示。
我知道agetty
从终端启动并不是通常的启动方式。
但是,我很好奇这是如何发生的以及如何在agetty
没有终端的情况下从终端开始任何连接(sudo
在已启动的终端上询问密码agetty
让我相信有是某些连接)到已启动它的终端。
我已经尝试过:
$ nohup agetty ttyS0 9600 &
$ disown <pid>
没有成功。
我很想知道这里起作用的潜在机制以及我如何解决这个问题*。
*如前所述,我知道agetty
从普通终端开始并不是使用该程序的方式。我仍然对这种情况如何发生以及如何防止agetty
这种行为感兴趣。
答案1
当您从终端运行时agetty
,它会成为新会话的会话领导者,并继承调用它的控制终端。因此,在虚拟机中执行的任何命令都会继承控制终端,从而影响 sudo 等实用程序的行为。启动agetty 的终端被用作控制终端,这就是sudo 提示输入密码的原因。
终端会话通常与会话 ID (SID) 相关联,会话中运行的每个进程都属于该 SID。设置SID的进程称为会话领导者。当agetty启动时,它成为新会话的会话领导者并继承控制终端。因此,当您sudo
在虚拟机中运行时,启动agetty进程的终端上会出现输入密码的提示。
要切断agetty和终端之间的连接,您可以使用setsid命令:
setsid agetty ttyS0 9600
该setsid
命令创建一个新会话,并将调用的命令设置为新会话的会话领导者。此操作还将命令设置为没有控制终端,从而确保新会话中生成的进程(如sudo
)不会继承原始控制终端。从的 POSIX 规范setsid
:
如果调用进程不是进程组领导者,setsid() 函数将创建一个新会话。返回后,调用进程应是该新会话的会话领导者,应是新进程组的进程组领导者,并且不应具有控制终端。调用进程的进程组ID 应设置为等于调用进程的进程ID。调用进程应该是新进程组中的唯一进程和新会话中的唯一进程。
您可能会问为什么sudo
首先关心这个控制终端。该sudo
命令尽力与调用它的用户直接交互,而无需通过任何中介,这与仅仅写入 stdout 或 stderr 不同。写入 stdout 或 stderr 可能存在泄露敏感信息的风险,或者其他进程可能会窃听此数据。
控制终端被认为更接近用户的直接线路,使其成为密码输入等用户敏感操作的更安全的通道。调用 sudo 时,它通常会绕过 stdout 或 stderr 将密码提示直接写入终端。这样做是为了确保密码提示对用户可见,并且不会意外重定向、记录或以其他方式捕获。