Skip to content

Commit 2a8f13d

Browse files
process path parsing improvements
- Preprend the chroot of a process to the path: Even if a process is executed from a chroot (or a different mount namespace), the kernel reports the path as it were from the host. For chrooted processes, now we prepend the chroot to the path: Instead of /usr/bin/curl -> /var/cache/pbuilder/usr/bin/curl Note: if the process is emulated (with qemu for example), the checksum will be of the emulator. - For proceses launched from /proc, like /proc/self/exe or /proc/<pid>/fd/<number>, we were replacing in the cmdline "/proc/*" with the absolute path of the binary: Path: /usr/bin/curl Cmdline reported: /proc/<pid>/fd/<number> -L 1.1.1.1 Cmdline "fixed": /usr/bin/curl -L 1.1.1.1 Now we only do that for /proc/self/exe, and leave /proc/<pid>/fd/<number> as is, since Path is already the absolute path to the binary. Path: /usr/bin/spotify Cmdline reported: /proc/self/exe Cmdline "fixed": /usr/bin/spotify Path: /usr/bin/curl Cmdline reported: /proc/<pid>/fd/<number> -L 1.1.1.1 Cmdline final: /proc/<pid>/fd/<number> -L 1.1.1.1
1 parent fb16dad commit 2a8f13d

File tree

2 files changed

+50
-6
lines changed

2 files changed

+50
-6
lines changed

daemon/procmon/details.go

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -229,9 +229,31 @@ func (p *Process) ReadExeLink() (string, error) {
229229
return os.Readlink(p.pathExe)
230230
}
231231

232+
// ReadRoot obtains the root directory of the process.
233+
// This symlink may point to any directory:
234+
// - Usually it'll point to the root filesystem (/).
235+
// - If the process chroot-ed to some dir, it'll point to it:
236+
// /proc/1234/root -> /etc/avahi
237+
// /proc/2345/root -> /var/cache/pbuilder/build/...
238+
// /proc/3456/root -> /proc
239+
// In some cases it'll point to a fs root, and others no.
240+
func (p *Process) ReadRoot() {
241+
if p.Root != "" {
242+
return
243+
}
244+
defer func() {
245+
log.Info("ReadRoot() %s -> %s", p.Path, p.Root)
246+
}()
247+
if root, err := os.Readlink(p.pathRoot); err == nil {
248+
p.Root = root
249+
return
250+
}
251+
p.Root = "/"
252+
}
253+
232254
// ReadPath reads the symbolic link that /proc/<pid>/exe points to.
233255
// Note 1: this link might not exist on the root filesystem, it might
234-
// have been executed from a container, so the real path would be:
256+
// have been executed from a container or a chroot, so the real path would be:
235257
// /proc/<pid>/root/<path that 'exe' points to>
236258
//
237259
// Note 2:
@@ -268,10 +290,21 @@ func (p *Process) ReadPath() error {
268290
func (p *Process) SetPath(path string) {
269291
p.Path = path
270292
p.CleanPath()
271-
p.RealPath = core.ConcatStrings(p.pathRoot, "/", p.Path)
293+
p.ReadRoot()
294+
295+
p.RealPath = core.ConcatStrings(p.pathRoot, p.Path)
272296
if core.Exists(p.RealPath) == false {
273297
p.RealPath = p.Path
274-
// p.CleanPath() ?
298+
}
299+
300+
// /proc/<pid>/root may point to any directory.
301+
// if a process chroot's to a directory, that's what it will point to.
302+
// It may be a fs root, or any random dir with the minimum files needed.
303+
if p.Root != "/" && !strings.HasPrefix(p.Path, p.Root) {
304+
chrootPath := core.ConcatStrings(p.Root, p.Path)
305+
if core.Exists(chrootPath) {
306+
p.Path = chrootPath
307+
}
275308
}
276309
}
277310

@@ -306,8 +339,10 @@ func (p *Process) ReadCmdline() {
306339
// CleanArgs applies fixes on the cmdline arguments.
307340
// - AppImages cmdline reports the execuable launched as /proc/self/exe,
308341
// instead of the actual path to the binary.
342+
// - For processes launched from a file descriptor, leave them with the orig
343+
// path, which usually starts with /proc/*/fd/<number>.
309344
func (p *Process) CleanArgs() {
310-
if len(p.Args) > 0 && p.Args[0] == ProcSelf {
345+
if len(p.Args) > 0 && p.Args[0] == ProcSelfExe {
311346
p.Args[0] = p.Path
312347
}
313348
}
@@ -401,14 +436,17 @@ func (p *Process) readStatus() {
401436
// - Remove extra characters from the link that it points to.
402437
// When a running process is deleted, the symlink has the bytes " (deleted")
403438
// appended to the link.
404-
// - If the path is /proc/self/exe, resolve the symlink that it points to.
439+
// - If the path is /proc/self/exe or /proc/<pid>/fd/<number>, resolve the symlink
440+
// that it points to.
405441
func (p *Process) CleanPath() {
406442

407443
// Sometimes the path to the binary reported is the symbolic link of the process itself.
408444
// This is not useful to the user, and besides it's a generic path that can represent
409445
// to any process.
410446
// Therefore we cannot use /proc/self/exe directly, because it resolves to our own process.
411-
if strings.HasPrefix(p.Path, ProcSelf) {
447+
// Same for /proc/<pid>/fd/<number>
448+
if strings.HasPrefix(p.Path, ProcPrefix) {
449+
fmt.Println("CleanPath, /proc/self prefix:", p.Path, p.Args)
412450
if link, err := os.Readlink(p.pathExe); err == nil {
413451
p.Path = link
414452
return

daemon/procmon/process.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ const (
2323
MethodEbpf = "ebpf"
2424

2525
KernelConnection = "Kernel connection"
26+
ProcPrefix = "/proc"
2627
ProcSelf = "/proc/self/"
28+
ProcSelfExe = "/proc/self/exe"
2729

2830
HashMD5 = "process.hash.md5"
2931
HashSHA1 = "process.hash.sha1"
@@ -93,6 +95,10 @@ type Process struct {
9395
// Path is the absolute path to the binary
9496
Path string
9597

98+
// Root is the root directory of the process.
99+
// Usually /, but if the process is in a chroot it'll be /whatever/a/b/c
100+
Root string
101+
96102
// RealPath is the path to the binary taking into account its root fs.
97103
// The simplest form of accessing the RealPath is by prepending /proc/<pid>/root/ to the path:
98104
// /usr/bin/curl -> /proc/<pid>/root/usr/bin/curl

0 commit comments

Comments
 (0)