fortify/sandbox/vfs/mountinfo.go
Ophestra 5098b12e4a
All checks were successful
Test / Create distribution (push) Successful in 25s
Test / Fortify (push) Successful in 2m39s
Test / Fpkg (push) Successful in 3m37s
Test / Data race detector (push) Successful in 4m10s
Test / Flake checks (push) Successful in 52s
sandbox/vfs: count mountinfo entries
Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-03-21 12:14:33 +09:00

146 lines
3.6 KiB
Go

package vfs
import (
"bufio"
"errors"
"fmt"
"io"
"strconv"
"strings"
)
var (
ErrMountInfoFields = errors.New("unexpected field count")
ErrMountInfoEmpty = errors.New("unexpected empty field")
ErrMountInfoDevno = errors.New("bad maj:min field")
ErrMountInfoSep = errors.New("bad optional fields separator")
)
type (
// MountInfo represents a /proc/pid/mountinfo document.
MountInfo struct {
Next *MountInfo
MountInfoEntry
}
// MountInfoEntry represents a line in /proc/pid/mountinfo.
MountInfoEntry struct {
// mount ID: a unique ID for the mount (may be reused after umount(2)).
ID int `json:"id"`
// parent ID: the ID of the parent mount (or of self for the root of this mount namespace's mount tree).
Parent int `json:"parent"`
// major:minor: the value of st_dev for files on this filesystem (see stat(2)).
Devno DevT `json:"devno"`
// root: the pathname of the directory in the filesystem which forms the root of this mount.
Root string `json:"root"`
// mount point: the pathname of the mount point relative to the process's root directory.
Target string `json:"target"`
// mount options: per-mount options (see mount(2)).
VfsOptstr string `json:"vfs_optstr"`
// optional fields: zero or more fields of the form "tag[:value]"; see below.
// separator: the end of the optional fields is marked by a single hyphen.
OptFields []string `json:"opt_fields"`
// filesystem type: the filesystem type in the form "type[.subtype]".
FsType string `json:"fstype"`
// mount source: filesystem-specific information or "none".
Source string `json:"source"`
// super options: per-superblock options (see mount(2)).
FsOptstr string `json:"fs_optstr"`
}
DevT [2]int
)
// ParseMountInfo parses a mountinfo file according to proc_pid_mountinfo(5).
func ParseMountInfo(r io.Reader) (*MountInfo, int, error) {
var m, cur *MountInfo
s := bufio.NewScanner(r)
var n int
for s.Scan() {
n++
if cur == nil {
m = new(MountInfo)
cur = m
} else {
cur.Next = new(MountInfo)
cur = cur.Next
}
// prevent proceeding with misaligned fields due to optional fields
f := strings.Split(s.Text(), " ")
if len(f) < 10 {
return nil, -1, ErrMountInfoFields
}
// 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
// (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11)
// (1) id
if id, err := strconv.Atoi(f[0]); err != nil { // 0
return nil, -1, err
} else {
cur.ID = id
}
// (2) parent
if parent, err := strconv.Atoi(f[1]); err != nil { // 1
return nil, -1, err
} else {
cur.Parent = parent
}
// (3) maj:min
if n, err := fmt.Sscanf(f[2], "%d:%d", &cur.Devno[0], &cur.Devno[1]); err != nil {
return nil, -1, err
} else if n != 2 {
// unreachable
return nil, -1, ErrMountInfoDevno
}
// (4) mountroot
cur.Root = Unmangle(f[3])
if cur.Root == "" {
return nil, -1, ErrMountInfoEmpty
}
// (5) target
cur.Target = Unmangle(f[4])
if cur.Target == "" {
return nil, -1, ErrMountInfoEmpty
}
// (6) vfs options (fs-independent)
cur.VfsOptstr = Unmangle(f[5])
if cur.VfsOptstr == "" {
return nil, -1, ErrMountInfoEmpty
}
// (7) optional fields, terminated by " - "
i := len(f) - 4
cur.OptFields = f[6:i]
// (8) optional fields end marker
if f[i] != "-" {
return nil, -1, ErrMountInfoSep
}
i++
// (9) FS type
cur.FsType = Unmangle(f[i])
if cur.FsType == "" {
return nil, -1, ErrMountInfoEmpty
}
i++
// (10) source -- maybe empty string
cur.Source = Unmangle(f[i])
i++
// (11) fs options (fs specific)
cur.FsOptstr = Unmangle(f[i])
}
return m, n, s.Err()
}