github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/sandbox/cgroup/scanning.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package cgroup 21 22 import ( 23 "os" 24 "path/filepath" 25 "regexp" 26 "strings" 27 28 "github.com/snapcore/snapd/snap/naming" 29 ) 30 31 var ( 32 // string that looks like a hook security tag 33 roughHookTagPattern = regexp.MustCompile(`snap\.[^.]+\.hook\.[^.]+`) 34 // string that looks like an app security tag 35 roughAppTagPattern = regexp.MustCompile(`snap\.[^.]+\.[^.]+`) 36 ) 37 38 // securityTagFromCgroupPath returns a security tag from cgroup path. 39 func securityTagFromCgroupPath(path string) naming.SecurityTag { 40 leaf := filepath.Base(filepath.Clean(path)) 41 42 // If the security cgroup name doesn't start with "snap." then there is no 43 // point in doing other checks. 44 if !strings.HasPrefix(leaf, "snap.") { 45 return nil 46 } 47 48 // We are only interested in cgroup directory names that correspond to 49 // services and scopes, as they contain processes that have been invoked 50 // from a snap. 51 // Expected format of leaf name: 52 // snap.<pkg>.<app>.service - assigned by systemd for services 53 // snap.<pkg>.<app>.<uuid>.scope - transient scope for apps 54 // snap.<pkg>.hook.<app>.<uuid>.scope - transient scope for hooks 55 if ext := filepath.Ext(leaf); ext != ".service" && ext != ".scope" { 56 return nil 57 } 58 59 // There are two broad forms expressed by the pair of regular expressions defined above. 60 for _, re := range []*regexp.Regexp{roughHookTagPattern, roughAppTagPattern} { 61 if maybeTag := re.FindString(leaf); maybeTag != "" { 62 if tag, err := naming.ParseSecurityTag(maybeTag); err == nil { 63 return tag 64 } 65 } 66 } 67 return nil 68 } 69 70 // PidsOfSnap returns the association of security tags to PIDs. 71 // 72 // NOTE: This function returns a reliable result only if the refresh-app-awareness 73 // feature was enabled since all processes related to the given snap were started. 74 // If the feature wasn't always enabled then only service process are correctly 75 // accounted for. 76 // 77 // The return value is a snapshot of the pids for a given snap, grouped by 78 // security tag. The result may be immediately stale as processes fork and 79 // exit. 80 // 81 // Importantly, if the per-snap lock is held while computing the set, then the 82 // following guarantee is true: if a security tag is not among the results then 83 // no such tag can come into existence while the lock is held. 84 // 85 // This can be used to classify the activity of a given snap into activity 86 // classes, based on the nature of the security tags encountered. 87 func PidsOfSnap(snapInstanceName string) (map[string][]int, error) { 88 // pidsByTag maps security tag to a list of pids. 89 pidsByTag := make(map[string][]int) 90 91 // Walk the cgroup tree and look for "cgroup.procs" files. Having found one 92 // we try to derive the snap security tag from it. If successful and the 93 // tag matches the snap we are interested in, we harvest the snapshot of 94 // PIDs that belong to the cgroup and put them into a bucket associated 95 // with the security tag. 96 walkFunc := func(path string, fileInfo os.FileInfo, err error) error { 97 if err != nil { 98 // See the documentation of path/filepath.Walk. The error we get is 99 // the error that was encountered while walking. We just surface 100 // that error quickly. 101 return err 102 } 103 if fileInfo.IsDir() { 104 // We don't care about directories. 105 return nil 106 } 107 if filepath.Base(path) != "cgroup.procs" { 108 // We are looking for "cgroup.procs" files. Those contain the set 109 // of processes that momentarily inhabit a cgroup. 110 return nil 111 } 112 // Now that we are confident that the file we're looking at is 113 // interesting, extract the security tag from the cgroup path and check 114 // if the security tag belongs to the snap we are interested in. Since 115 // not all cgroups are related to snaps it is not an error if the 116 // cgroup path does not denote a snap. 117 cgroupPath := filepath.Dir(path) 118 parsedTag := securityTagFromCgroupPath(cgroupPath) 119 if parsedTag == nil { 120 return nil 121 } 122 if parsedTag.InstanceName() != snapInstanceName { 123 return nil 124 } 125 pids, err := pidsInFile(path) 126 if err != nil { 127 return err 128 } 129 tag := parsedTag.String() 130 pidsByTag[tag] = append(pidsByTag[tag], pids...) 131 // Since we've found the file we are looking for (cgroup.procs) we no 132 // longer need to scan the remaining files of this directory. 133 return filepath.SkipDir 134 } 135 136 var cgroupPathToScan string 137 ver, err := Version() 138 if err != nil { 139 return nil, err 140 } 141 if ver == V2 { 142 // In v2 mode scan all of /sys/fs/cgroup as there is no specialization 143 // anymore (each directory represents a hierarchy with equal 144 // capabilities and old split into controllers is gone). 145 cgroupPathToScan = filepath.Join(rootPath, cgroupMountPoint) 146 } else { 147 // In v1 mode scan just /sys/fs/cgroup/systemd as that is sufficient 148 // for finding snap-specific cgroup names. Systemd uses this for 149 // tracking and scopes and services are represented there. 150 cgroupPathToScan = filepath.Join(rootPath, cgroupMountPoint, "systemd") 151 } 152 // NOTE: Walk is internally performed in lexical order so the output is 153 // deterministic and we don't need to sort the returned aggregated PIDs. 154 if err := filepath.Walk(cgroupPathToScan, walkFunc); err != nil { 155 if os.IsNotExist(err) { 156 return nil, nil 157 } 158 return nil, err 159 } 160 161 return pidsByTag, nil 162 }