Skip to content

Go 主机安全面试:Linux 文件描述符转移与敏感资源复用检测 ​

主机安全常把“打开敏感文件”和“建立外联”当作检测入口。但进程可以拿到别人已经打开的文件描述符,随后直接读取文件或使用已有 socket,整个过程中未必再次出现对应的 openat 或 connect。

这不是说这些操作绕过了所有安全检查,而是提醒检测研发:资源最初由谁打开,与后来由谁使用,可能不是同一个进程。Go Agent 要把资源使用关系补进攻击链,同时识别正常的服务交接和 IPC,避免看到 fd 转移就告警。

岗位场景 ​

text
服务 A 打开敏感文件或建立网络连接
  -> 进程 B 通过 SCM_RIGHTS 接收 fd,或通过 pidfd_getfd 复制 fd
  -> B 使用该 fd 读取、写入或发送数据
  -> Go Agent 关联资源、转移方式、调用结果和进程身份
  -> 区分正常服务交接、失败尝试和异常资源使用
  -> 输出证据等级、攻击链时间线和采集缺口

一个典型客户问题是:“告警指向 Web 服务子进程读凭据,但日志里没有它打开凭据文件的记录。”排查时既要考虑采集丢失,也要考虑 fd 继承、复制和转移。本文聚焦后者,不把“没有采到打开事件”直接判为规避审计。

高频面试题 ​

1. fd 是什么?为什么不能用 fd 数字当资源 ID? ​

简洁答案: fd 是进程描述符表中的索引,类似进程手里的取件号码。同一个数字在不同进程中可以指向不同对象,关闭后也能被复用;fd 指向的 open file description 才保存文件偏移、文件状态标志等信息。

关键知识点:

  • “fd 为 7”必须带上进程身份和事件时间才有意义,不能跨进程直接关联。
  • dup、fork 继承和 fd 转移可能让多个描述符指向同一个 open file description,因此可能共享文件偏移和文件状态标志。
  • FD_CLOEXEC 是描述符级标志,与底层对象的文件状态标志不同。
  • 文件路径可能被重命名或删除;device + inode 可以辅助关联,但仍要考虑文件生命周期、inode 复用和命名空间。

Go 落地思路:

  • 进程键至少包含 host_id、boot_id、宿主机 PID 和启动时间;容器 PID、namespace ID 作为额外上下文。
  • 描述符映射用“进程键 + fd + 分配代次”,看到 close 或重新分配时更新;事件缺失时把映射标成不确定。
  • 文件对象保存采集时的设备号、inode 和路径快照;socket 保存网络命名空间及可取得的对象标识。不要声称这些字段单独就能证明共享 open file description。

2. SCM_RIGHTS 和 pidfd_getfd 有什么区别? ​

简洁答案: SCM_RIGHTS 通过 Unix socket 的控制消息传递已打开的 fd,需要发送方配合;pidfd_getfd 通过目标进程的 pidfd 和目标 fd,在调用进程中复制出一个 fd,不需要目标主动发送,但必须通过权限检查。

对比项SCM_RIGHTSpidfd_getfd
入口sendmsg/recvmsg 等消息接口的辅助数据系统调用,参数包括 pidfd、目标 fd 和 flags
是否需要目标配合需要发送方发送 fd不需要目标配合
主要检查边界IPC 可达性、对端身份、应用协议与适用的内核安全检查PTRACE_MODE_ATTACH_REALCREDS 权限检查
新描述符从哪里取得接收端控制消息中的 fd 数组成功调用的返回值
原来的 fd 是否消失不会自动从发送端移除不会从目标进程移除

关键知识点:

  • SCM_RIGHTS 传递的是对资源的引用,接收端得到的 fd 数字通常不同于发送端。
  • pidfd_getfd 从 Linux 5.6 开始提供,当前 flags 必须为 0;成功返回的描述符带 FD_CLOEXEC。
  • 两者都不是天然恶意行为。服务热重启、特权分离、浏览器沙箱和诊断工具都有合理使用场景。

Go 落地思路:

  • 将 transfer_method 标成 scm_rights、pidfd_getfd 或 inherit,保留证据来源。
  • 成功的 pidfd_getfd 事件中,调用方是新持有者,目标进程是原持有者;SCM_RIGHTS 的发送方和接收方则分别取自发送、接收证据。
  • 保留调用返回值、错误和目标 fd,不用“出现 API”替代“成功取得资源”。

3. 拿到 pidfd 就能任意复制别人的 fd 吗? ​

简洁答案: 不能。pidfd 提供对进程对象的引用,不是任意访问该进程资源的授权。pidfd_getfd 有独立的 ptrace 访问权限检查,还会受到相关安全策略限制。

关键知识点:

  • 同 UID 不保证调用成功,还涉及目标的 dumpable 状态、能力、用户命名空间和 LSM/Yama 等条件。
  • 成功获得 pidfd,不代表后续操作都被允许;目标 fd 关闭、进程退出也会导致失败。
  • 不能把所有 EPERM 都归因于 Yama;seccomp 等也可能拒绝系统调用。
  • 返回 EBADF 可能是 pidfd 无效,也可能是目标 fd 不再存在,排障要结合参数和前序事件。

Go 落地思路:

  • 区分“尝试”“复制成功”“后续使用已观察”三个状态。失败尝试可以作为链路线索,但不能写成凭据已经泄露。
  • 建立 pidfd 与目标进程的映射时,要处理 pidfd 自身的继承和转移;只有 pidfd_open 事件不足以覆盖全部来源。
  • Go 系统调用包装通常以 err 暴露失败,内核采集源可能记录负 errno;标准化时保留原始结果,并生成统一状态。

4. 为什么只监控敏感路径打开事件和网络 connect 不够? ​

简洁答案: 进程获得的可能是已经打开的文件或已经连接的 socket。它可以直接使用该对象,所以“资源使用者没有新的打开或连接事件”并不矛盾。

关键知识点:

  • fd 继承、dup 和转移都能改变资源持有关系。
  • 文件被删除后,已打开的 fd 仍可能引用它;此时路径快照不是当前路径状态。
  • 复制出来的 fd 保留原对象的访问模式;复制只读 fd 不会自动得到写权限。
  • 资源后续操作仍可能受其他检查约束,不能笼统称为“绕过所有沙箱”。

Go 落地思路:

  • 把“谁最初打开资源”和“谁后来使用资源”保存为两条关系,避免所有活动都归因到最初打开者。
  • 对可疑转移后的读取、写入或发送事件进行限时关联,明确是否观察到操作成功。
  • 不要让 Agent 为了确认内容而复制并读取客户的敏感 fd:这会产生额外访问,还可能改变共享文件偏移或消费 socket 数据。

5. eBPF、auditd 和 procfs 能直接还原全部 fd 转移吗? ​

简洁答案: 不能保证。系统调用记录、消息辅助数据、内核文件对象和用户态 fd 分配分别提供不同层次的证据,需要针对采集源验证覆盖范围。

关键知识点:

  • pidfd_getfd 的参数和返回值有利于识别复制动作,但把 pidfd 解析成目标进程仍需要额外信息。
  • 只看到 sendmsg/recvmsg 调用不能证明传了 fd,还需确认控制消息包含 SOL_SOCKET/SCM_RIGHTS。
  • sendmmsg/recvmmsg、兼容 ABI 和短命进程也会影响覆盖,不应宣称挂两个 syscall 就完整了。
  • procfs 是快照,受进程退出、fd 复用、权限和 namespace 影响,不能代替过去某次转移事件。
  • auditd 常规 syscall 参数记录不会自动解码全部辅助数据;eBPF 的对象解析能力也取决于挂载位置和内核版本。

Go 落地思路:

  • 对消息辅助数据设置解析字节上限和 fd 数量上限;超限记录 truncated,不把残缺结果当完整消息。
  • 系统调用入口、出口分别采集参数与结果,用线程身份关联在途调用,退出后清理;接收端辅助数据应按成功返回后的实际内容解释。
  • 上报 can_observe_transfer、can_resolve_object、丢失计数和解析失败计数,让规则知道当前证据能支持什么结论。
  • 先通过测试证明关键场景可观测,再决定是否需要更深的内核探针,避免为追求“全覆盖”无边界增加采集成本。

6. Go 自己接收 SCM_RIGHTS 时,要注意哪些边界? ​

简洁答案: 将业务数据和辅助数据分别接收,验证对端及消息类型,再解析 fd。收到的 fd 是真实资源,所有拒绝和错误路径都必须处理其生命周期。

关键知识点:

  • net.UnixConn.ReadMsgUnix 返回业务数据长度、辅助数据长度和 flags;WriteMsgUnix 可以发送这两类数据。
  • 标准库 syscall 的 UnixRights、ParseSocketControlMessage、ParseUnixRights 可用于理解和验证协议。工程已有 x/sys/unix 时可沿用;本文不要求新增依赖。
  • 检查 MSG_CTRUNC,否则辅助数据可能不完整。Linux 会关闭因接收缓冲区过小等原因未被交付的多余 fd,但应用已收到的 fd 仍由应用负责。
  • SO_PEERCRED 是连接或 socketpair 建立时的对端凭据快照;连接端点若被继承或转交,它不必等于每次实际发送消息的进程。
  • 如果协议需要逐条消息的发送者凭据,应评估 SO_PASSCRED/SCM_CREDENTIALS,同时保留授权检查,不能只凭 UID 判断业务权限。

Go 落地思路:

  • 只解析 oob[:oobn],先确认控制消息的 level/type,再接收 fd 数组;协议约定数量上限和允许的资源类型。
  • 解析时登记已收到的全部 fd;任何后续验证失败都关闭已登记资源。辅助数据截断的拒绝路径同样要释放已交付的 fd。
  • 用 fstat 等元数据验证对象类型,不根据发送方自报的“这是普通文件”直接信任。
  • 明确 fd 由哪一层持有和关闭,检查接收 API 的 close-on-exec 保障;底层 Linux recvmsg 可使用 MSG_CMSG_CLOEXEC,避免不必要的 exec 继承。

以上是 Agent 自有 IPC 的实现注意事项。被动观察第三方进程时不能假定可以读取其协议,也不需要为了取证主动建立连接或索取 fd。

7. 怎样关联成攻击链,同时降低正常 IPC 的误报? ​

简洁答案: 先判断转移是否成功,再判断资源敏感性、接收进程来源和后续行为。正常服务交接通常有稳定的服务身份、对端和资源类型;异常链路会出现与角色不匹配的组合。

关键知识点:

  • 正常例子:服务升级时,将监听 socket 从旧进程交接给经过验证的新进程。
  • 可疑例子:Web 服务派生的临时目录程序成功取得凭据文件 fd,随后出现读取与异常外联证据。
  • “监听 socket 交接”和“已连接 socket 复用”是不同场景,规则不能混在一起。
  • 时间邻近只能支持关联,不能证明外联内容就是此前读取的凭据;没有内容或更强证据时保留这一边界。

Go 落地思路:

  • 先按转移类型和资源类别筛选候选事件,再在有界时间窗内关联进程链、文件使用和网络活动。
  • 白名单采用“服务身份 + 经验证的执行文件 + 预期对端 + 资源类别 + 有效期”,避免只信任程序名或放行整个 /tmp。
  • 告警输出命中的证据和缺失项,例如“复制成功;观察到读取;出现外联;数据内容未知”,不要自动升级为“已窃取凭据”。
  • AI 可以辅助整理链路、解释字段,判定依据仍由可复核的事件和规则提供。

8. Go Agent 怎样控制 CPU、内存和隐私成本? ​

简洁答案: 优先采集转移的元数据,对候选事件做补充关联;限制缓存容量和解析预算,默认不读取文件内容或消息业务载荷。

关键知识点:

  • 文件内容、socket 数据、全部环境变量容易扩大隐私暴露和采集成本,通常不是判断 fd 转移所必需的。
  • 扫描所有进程的 fd 表既昂贵又存在竞态,不能把扫描频率当作准确性的保证。
  • 单个进程可能持有大量 fd,缓存必须同时限制总量和单进程配额。
  • 丢事件和缓存淘汰会影响归因可信度,不能静默删除后继续输出确定结论。

Go 落地思路:

  • 保留必要的进程身份、fd、对象元数据、方法、返回值与证据来源,敏感路径按客户策略脱敏。
  • 使用有容量上限的队列和 worker,避免每个事件创建 goroutine;复用现有上报与背压机制。
  • 对转移映射设置 TTL 和容量上限;删除过期关系时保留必要的丢失统计。
  • 监控队列深度、事件延迟、解析失败、缓存淘汰和 fd 数量,以固定回放数据测量 CPU 与内存变化。

9. 客户反馈“没有 open 记录却出现凭据读取”,怎么排查? ​

简洁答案: 先验证实际读取证据和进程身份,再检查采集覆盖、fd 来源和缓存完整性。最后才决定是正常复用、检测误关联,还是可疑资源使用。

关键知识点:

  • PID、fd 和 inode 都可能复用,错误关联会制造看起来完整的攻击链。
  • 进程很快退出时,事后 procfs 查询可能已经失去意义。
  • 读取调用失败、复制失败和成功行为应分开统计,不能只看事件名称。
  • root 权限也不能保证所有策略都允许访问,排查权限时保留具体错误、内核和策略上下文。

Go 落地思路:

  1. 核对 host、boot、进程启动时间、采集源和事件时间,排除跨进程、跨重启误关联。
  2. 查找成功的打开、继承、复制或接收事件,确认 fd 当时指向什么对象;来源缺失就标成未知。
  3. 检查队列丢失、时间窗、缓存淘汰、辅助数据截断和兼容 ABI 覆盖,避免把观测缺口当攻击证据。
  4. 验证服务升级、沙箱 broker、诊断操作等合法背景,再检查异常进程链和后续资源使用。
  5. 用脱敏回放样本调整规则并记录版本;不能凭一例误报就把全部 fd 转移行为放行。

学习要点 ​

能力必须掌握的知识面试中应讲清的边界
Linux 文件模型fd、open file description、dup、继承、FD_CLOEXEC描述符数字不等于全局资源身份
资源转移SCM_RIGHTS、pidfd_getfd、ptrace 权限检查目标配合与访问授权是不同条件
事件采集入口/出口、辅助数据、返回值、procfs 快照转移尝试不等于成功使用
Go IPCReadMsgUnix、控制消息解析、fd 关闭截断和拒绝路径仍要释放已接收资源
攻击链与降噪服务身份、资源敏感性、进程链、后续操作时间相关不等于泄露内容已确认
线上治理有界缓存、覆盖指标、丢失与隐私控制缺失证据必须降低结论确定性

小练习与复盘题 ​

仅在隔离的 Linux 测试环境使用自建进程、普通测试文件和本地 socket,不访问客户凭据或业务连接。

  1. 让两个测试进程通过 Unix socket 传递临时文件 fd,确认两端 fd 数字不需要相同,且接收方无需重新打开文件路径。
  2. 验证共享文件偏移:文件写入 abcdef 后从偏移 0 开始,发送端先读取两字节,再让接收端读取,观察是否从 cd 开始;对比重新打开文件的情况。
  3. 设计 pidfd_getfd 的成功、权限拒绝、目标 fd 已关闭三类用例,结合测试环境权限解释结果,不为了让用例成功而关闭安全策略。
  4. 模拟辅助数据缓冲区过小和协议拒绝,验证已收到的 fd 会被清理,持续运行时进程 fd 数量不会单调增长。
  5. 回放“正常监听 socket 交接”“Web 子进程成功复制敏感文件 fd”“复制失败后正常退出”,写出每类结果的证据等级和抑制依据。
  6. 人为删除一条转移事件,确认检测结果显示“资源来源未知”,而不是凭后续 fd 数字反推不存在的成功转移。
  7. 模拟进程退出、同 PID 新进程启动、相同 fd 被重新分配,验证关联不会跨越进程启动时间和描述符生命周期。
  8. 在固定事件集下记录 Go Agent 的 CPU、内存、队列延迟和缓存淘汰次数,说明预算不足时优先保留哪些证据。

复盘时用四句话回答:谁取得了什么资源?通过哪种方式?哪一步已证实成功?当前还有哪些证据缺口? 这比只列系统调用名称更能体现主机安全研发能力。

参考资料 ​

最近更新