强制关闭文件描述符的最安全方法

Rei*_*ica 8 linux unmounting file-descriptors gdb

有时您需要卸载文件系统或分离循环设备,但这是busy因为打开的文件描述符,可能是因为smb服务器进程。

要强制卸载,您可以终止有问题的进程(或 try kill -SIGTERM),但这会关闭smb连接(即使它打开的某些文件不需要关闭)。

这里描述了一种强制进程关闭给定文件描述符的hacky方法,使用gdb调用close(fd). 然而,这似乎很危险。如果关闭的描述符被回收怎么办?该进程可能会使用旧的存储描述符而没有意识到它现在指的是一个完全不同的文件。

我有一个想法,但不知道它有什么样的缺陷:使用gdb,打开/dev/null方式O_WRONLY(编辑:建议O_PATH作为更好的选择的评论),然后dup2关闭有问题的文件描述符并将其描述符重用于/dev/null. 这样,对文件描述符的任何读取或写入都将失败。

像这样:

sudo gdb -p 234532
(gdb) set $dummy_fd = open("/dev/null", 0x200000) // O_PATH
(gdb) p dup2($dummy_fd, offending_fd)
(gdb) p close($dummy_fd)
(gdb) detach
(gdb) quit
Run Code Online (Sandbox Code Playgroud)

什么可能出错?

thr*_*rig 2

摆弄流程gdb几乎从来都不安全,但如果出现紧急情况并且流程需要保持开放并且了解所有风险和涉及的代码,则可能有必要。

大多数情况下,我会简单地终止该进程,尽管某些情况可能有所不同,并且可能取决于环境、谁拥有所涉及的相关系统和进程、该进程正在做什么、是否有关于“可以杀死它”或“的文档”不,先联系某某”等等。一旦尘埃落定,这些细节可能需要在事后分析会议上制定。如果有计划的迁移,最好提前检查是否有任何进程打开了有问题的文件描述符,以便可以在非紧急情况下处理这些文件描述符(cron 作业或仅在迁移时凌晨运行的其他计划任务)如果您只在白天检查,可能会很容易错过)。

只写与读取与读写

您重新打开文件描述符的想法O_WRONLY是有问题的,因为并非所有文件描述符都是只写的。John Viega 和 Matt Messier 在《C 和 C++ 安全编程指南》一书中采用了更细致的方法,以不同于标准输出和标准错误的方式处理标准输入(第 25 页,“安全管理文件描述符”):

static int open_devnull(int fd) {
  FILE *f = 0;

  if (!fd) f = freopen(_PATH_DEVNULL, "rb", stdin);
  else if (fd == 1) f = freopen(_PATH_DEVNULL, "wb", stdout);
  else if (fd == 2) f = freopen(_PATH_DEVNULL, "wb", stderr);
  return (f && fileno(f) == fd);
}
Run Code Online (Sandbox Code Playgroud)

在这种gdb情况下,需要检查描述符(或FILE *句柄)是否是只读、读写或只写,并在 上打开适当的替换/dev/null。如果不是,如果进程尝试从中读取,曾经只读的句柄现在变成了只写,将会导致不必要的错误。

可能会出现什么问题?

当文件描述符(也可能是 FILE *句柄)在幕后被篡改时,进程的行为究竟如何将取决于进程,并且如果该描述符永远不会用于“噩梦模式”,则该进程的行为将与“没什么大不了”有所不同。由于未刷新的数据、没有文件正确关闭指示器或其他一些意外问题,某处的文件已损坏。

对于句柄,在关闭句柄之前FILE *添加调用可能会有所帮助,或者可能会导致双缓冲或其他一些问题; 这是在不确切知道源代码的作用和期望的情况fflush(3)下进行随机调用的几个危险之一。 软件还可能在描述符或句柄gdb之上构建额外的复杂层,这些层也可能需要处理。猴子修补代码可以很容易地变成活动扳手。fdFILE *

概括

向进程发送标准终止信号应该给它一个正确关闭资源的机会,就像系统正常关闭一样。摆弄流程gdb可能不会正确地结束事情,并且可能会使情况变得更糟。