监视Linux中正在运行的进程的复杂性

每个人都知道如何监视Linux系统上正在运行的进程。但是,几乎没有人在这样的观察中达到高精度。实际上,本材料中将讨论的所有监视过程的方法都缺少一些内容。 在开始实验之前,让我们定义一个过程监视系统的需求:











  1. 有关所有进程的信息,即使是短暂的信息也应记录下来。
  2. 我们应该了解所有正在运行的进程的可执行文件的完整路径的信息。
  3. 在合理的范围内,我们不需要针对内核的不同版本修改或重新编译我们的代码。
  4. : - Kubernetes Docker, , / . cgroup ID . , , «» « ». , « », « », « », API, Docker . ID , . Docker .


让我们谈谈可以帮助完成此任务的常见Linux API。为了不使故事复杂化,我们将特别注意使用系统调用创建的过程execve如果我们要讨论一个更完整的问题解决方案,那么在其实现期间,还必须监视使用系统调用fork/clone及其变体创建的过程以及调用的结果execveat



在用户模式下实施的简单解决方案



  1. 正在联络/proc由于过程周期短的问题,这种方法并不特别适合我们。
  2. netlink. netlink , PID . . /proc, .
  3. Linux. — , . API . . . — , , , . , API , auditd osquery. , , auditd go-audit从理论上讲,可以缓解此问题。但是,对于企业级解决方案,您无法事先知道客户是否使用了此类工具,以及是否使用了哪些工具。也无法事先知道客户端正在使用哪些直接与Auditing API配合使用的安全控制。第二个缺点是审核API对容器一无所知。尽管有这个问题已经讨论了很多年,但事实并非如此。


简单的内核模式调试工具



这些机制的实现允许在单个副本中使用各种类型的“探针”。



▍要点



使用跟踪点(tracepoint)。跟踪点是在编译过程中静态插入内核中特定位置的传感器。每个这样的传感器都可以独立于其他传感器打开,因此,当到达内核代码的嵌入位置时,它将发出通知。内核包含几个适合我们的跟踪点,这些跟踪点的代码在系统调用操作的各个点执行execve。这- ,sched_process_execopen_exec为了获得此列表,我运行了命令sys_enter_execvesys_exit_execvecat /sys/kernel/tracing/available_events | grep exec并使用读取内核代码获得的信息过滤结果列表。这些跟踪点比上面描述的机制更适合我们,因为它们使我们能够组织对短暂过程的观察。但是,如果参数exec是此类文件的相对路径,则它们都不提供有关该进程的可执行文件的完整路径的信息换句话说,如果用户执行like之类的命令cd /bin && ./ls,那么我们将以形式./ls而不是形式获得路径信息/bin/ls这是一个简单的例子:



#    the sched_process_exec
sudo -s
cd /sys/kernel/debug/tracing
echo 1 > events/sched/sched_process_exec/enable

#  ls    
cd /bin && ./ls

#      sched_process_exec
#    ,        
cd -
cat trace | grep ls

#   
echo 0 > events/sched/sched_process_exec/enable


proKprobe / Kretprobe传感器



传感器kprobe使您可以从内核中几乎任何地方提取调试信息。它们就像内核代码中的特殊断点一样,可以在不停止代码执行的情况下提供信息。kprobe与跟踪点不同,传感器可以连接到多种功能。这样的传感器的代码将在执行系统调用期间触发execve。但是我在调​​用图中没有找到execve任何函数,其参数既是PID进程又是其可执行文件的完整路径。结果,我们面临与使用跟踪点时相同的“相对路径问题”。在这里,您可以依靠特定内核的特性来“调整”某些内容。毕竟传感器kprobe可以从内核调用堆栈中读取数据。但是,这种解决方案在不同的内核版本中将无法稳定运行。因此,我不认为这一点。



e将eBPF程序与跟踪点配合使用,并使用kprobe和kretprobe探针



在这里,我们谈论的事实是,当执行某些代码时,将触发跟踪点或传感器,但是将执行eBPF程序的代码,而不是普通事件处理程序的代码。



使用这种方法为我们打开了一些新的可能性。现在,当我们进行系统调用时,可以在内核中运行任意代码execve。从理论上讲,这应该使我们能够从内核中提取我们需要的任何信息并将其发送到用户空间。有两种获取此类数据的方法,但没有一种可以满足上述要求。



  1. , task_struct linux_binprm. , , . , sched_process_exec , eBPF- , dentry bprm->file->f_path.dentry, . eBPF- . . , eBPF- , , , .
  2. eBPF . , . — API. — . (, eBPF, cgroup ID, ).


«»



  1. LD_PRELOAD exec libc. , . , , , , .
  2. execve, fork/clone chdir , . , execve execve . — eBPF- eBPF- , .
  3. , ptrace. -. — ptrace seccomp SECCOMP_RET_TRACE. seccomp execve , execve seccomp execve.
  4. 使用AppArmor。您可以编写一个AppArmor配置文件,以防止进程调用可执行文件。如果您在训练模式(投诉)下使用此配置文件,则AppArmor将不会禁止执行流程,而只会发出有关违反配置文件中指定规则的通知。如果您将配置文件连接到每个正在运行的进程,那么我们将获得一个有效的解决方案,但是却没有吸引力,而且过于“笨拙”。使用这种方法可能不值得。


其他解决方案



我必须立即说这些解决方案都不符合我们的要求,但是,我将列出它们:



  1. 使用该实用程序ps该工具仅解决/proc,因此遭受与指向相同的问题/proc
  2. execsnoop, eBPF. , , , kprobe/kretprobe, , , . , execsnoop , , , .
  3. execsnoop, eBPF. — kprobe, .




将来,将有可能使用尚不可用的辅助eBPF函数get_fd_path将其添加到内核后,对于解决我们的问题将很有用。的确,必须使用一种不提供从内核数据结构中读取信息的方法来获取该过程的可执行文件的完整路径。



结果



我们审查的所有API都不是完美的。下面,我想提供一些指导,说明使用哪些方法来获取有关流程的信息以及何时使用它们:



  1. auditd go-audit. , . , , , . , , , - API , , . — , .
  2. , , , , , execsnoop. — .
  3. , , , , , . , . , , eBPF- eBPF-, perf... 关于所有这些的故事值得在另一篇文章中介绍。选择这种监视过程的方法时,要记住的最重要的事项如下。如果使用eBPF程序,请检查其静态编译的可能性,这将使您不必依赖内核头文件。但是正是这种依赖性使得我们试图避免使用这种方法。使用此方法还意味着您不能使用内核数据结构,也不能使用BCC之类的框架在运行时编译eBPF程序。
  4. 如果您对短暂的过程不感兴趣,并且先前的建议不适合您,请将netlink功能与一起使用/proc


您如何组织对Linux中正在运行的进程的监视?










All Articles