gitee.com/mysnapcore/mysnapd@v0.1.0/interfaces/builtin/kernel_module_load.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2021 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 builtin 21 22 import ( 23 "errors" 24 "fmt" 25 "regexp" 26 "strings" 27 28 "gitee.com/mysnapcore/mysnapd/interfaces" 29 "gitee.com/mysnapcore/mysnapd/interfaces/kmod" 30 "gitee.com/mysnapcore/mysnapd/snap" 31 ) 32 33 const kernelModuleLoadSummary = `allows constrained control over kernel module loading` 34 35 const kernelModuleLoadBaseDeclarationPlugs = ` 36 kernel-module-load: 37 allow-installation: false 38 deny-auto-connection: true 39 ` 40 41 const kernelModuleLoadBaseDeclarationSlots = ` 42 kernel-module-load: 43 allow-installation: 44 slot-snap-type: 45 - core 46 deny-connection: true 47 ` 48 49 var modulesAttrTypeError = errors.New(`kernel-module-load "modules" attribute must be a list of dictionaries`) 50 51 // kernelModuleLoadInterface allows creating transient and persistent modules 52 type kernelModuleLoadInterface struct { 53 commonInterface 54 } 55 56 type loadOption int 57 58 const ( 59 loadNone loadOption = iota 60 loadDenied 61 loadOnBoot 62 loadDynamic 63 ) 64 65 type ModuleInfo struct { 66 name string 67 load loadOption 68 options string 69 } 70 71 var kernelModuleNameRegexp = regexp.MustCompile(`^[-a-zA-Z0-9_]+$`) 72 var kernelModuleOptionsRegexp = regexp.MustCompile(`^([a-zA-Z][a-zA-Z0-9_]*(=[[:graph:]]+)? *)+$`) 73 74 func enumerateModules(plug interfaces.Attrer, handleModule func(moduleInfo *ModuleInfo) error) error { 75 var modules []map[string]interface{} 76 err := plug.Attr("modules", &modules) 77 if err != nil && !errors.Is(err, snap.AttributeNotFoundError{}) { 78 return modulesAttrTypeError 79 } 80 81 for _, module := range modules { 82 name, ok := module["name"].(string) 83 if !ok { 84 return errors.New(`kernel-module-load "name" must be a string`) 85 } 86 87 var load loadOption 88 if loadAttr, found := module["load"]; found { 89 loadString, ok := loadAttr.(string) 90 if !ok { 91 return errors.New(`kernel-module-load "load" must be a string`) 92 } 93 94 switch loadString { 95 case "denied": 96 load = loadDenied 97 case "on-boot": 98 load = loadOnBoot 99 case "dynamic": 100 load = loadDynamic 101 default: 102 return fmt.Errorf(`kernel-module-load "load" value is unrecognized: %q`, loadString) 103 } 104 } 105 106 var options string 107 if optionsAttr, found := module["options"]; found { 108 options, ok = optionsAttr.(string) 109 if !ok { 110 return errors.New(`kernel-module-load "options" must be a string`) 111 } 112 } 113 114 moduleInfo := &ModuleInfo{ 115 name: name, 116 load: load, 117 options: options, 118 } 119 120 if err := handleModule(moduleInfo); err != nil { 121 return err 122 } 123 } 124 125 return nil 126 } 127 128 func validateNameAttr(name string) error { 129 if !kernelModuleNameRegexp.MatchString(name) { 130 return errors.New(`kernel-module-load "name" attribute is not a valid module name`) 131 } 132 133 return nil 134 } 135 136 func validateOptionsAttr(moduleInfo *ModuleInfo) error { 137 if moduleInfo.options == "" { 138 return nil 139 } 140 141 if moduleInfo.load == loadDenied { 142 return errors.New(`kernel-module-load "options" attribute incompatible with "load: denied"`) 143 } 144 145 dynamicLoadingWithAnyOptions := moduleInfo.load == loadDynamic && moduleInfo.options == "*" 146 if !dynamicLoadingWithAnyOptions && !kernelModuleOptionsRegexp.MatchString(moduleInfo.options) { 147 return fmt.Errorf(`kernel-module-load "options" attribute contains invalid characters: %q`, moduleInfo.options) 148 } 149 150 return nil 151 } 152 153 func validateModuleInfo(moduleInfo *ModuleInfo) error { 154 if err := validateNameAttr(moduleInfo.name); err != nil { 155 return err 156 } 157 158 if err := validateOptionsAttr(moduleInfo); err != nil { 159 return err 160 } 161 162 if moduleInfo.options == "" && moduleInfo.load == loadNone { 163 return errors.New(`kernel-module-load: must specify at least "load" or "options"`) 164 } 165 166 return nil 167 } 168 169 func (iface *kernelModuleLoadInterface) BeforeConnectPlug(plug *interfaces.ConnectedPlug) error { 170 numModulesEntries := 0 171 err := enumerateModules(plug, func(moduleInfo *ModuleInfo) error { 172 numModulesEntries++ 173 return validateModuleInfo(moduleInfo) 174 }) 175 if err != nil { 176 return err 177 } 178 179 if numModulesEntries == 0 { 180 return modulesAttrTypeError 181 } 182 183 return nil 184 } 185 186 func (iface *kernelModuleLoadInterface) KModConnectedPlug(spec *kmod.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { 187 snapInfo := plug.Snap() 188 commonDataDir := snapInfo.CommonDataDir() 189 190 err := enumerateModules(plug, func(moduleInfo *ModuleInfo) error { 191 var err error 192 switch moduleInfo.load { 193 case loadDenied: 194 err = spec.DisallowModule(moduleInfo.name) 195 case loadOnBoot: 196 err = spec.AddModule(moduleInfo.name) 197 if err != nil { 198 break 199 } 200 fallthrough 201 case loadNone, loadDynamic: 202 if len(moduleInfo.options) > 0 && moduleInfo.options != "*" { 203 // module options might include filesystem paths. Beside 204 // supporting hardcoded paths, it makes sense to support also 205 // paths to files provided by the snap; for this reason, we 206 // support expanding the $SNAP_COMMON variable here. 207 // We do not use os.Expand() because that supports both $ENV 208 // and ${ENV}, and we'd rather not alter the options which 209 // contain a "$" but are not meant to be expanded. Instead, 210 // just look for the "$SNAP_COMMON/" string and replace it; the 211 // extra "/" at the end ensures that the variable is 212 // terminated. 213 options := strings.ReplaceAll(moduleInfo.options, "$SNAP_COMMON/", commonDataDir+"/") 214 err = spec.SetModuleOptions(moduleInfo.name, options) 215 } 216 default: 217 // we can panic, this will be catched on validation 218 panic("Unsupported module load option") 219 } 220 return err 221 }) 222 return err 223 } 224 225 func (iface *kernelModuleLoadInterface) AutoConnect(*snap.PlugInfo, *snap.SlotInfo) bool { 226 return true 227 } 228 229 func init() { 230 registerIface(&kernelModuleLoadInterface{ 231 commonInterface: commonInterface{ 232 name: "kernel-module-load", 233 summary: kernelModuleLoadSummary, 234 baseDeclarationPlugs: kernelModuleLoadBaseDeclarationPlugs, 235 baseDeclarationSlots: kernelModuleLoadBaseDeclarationSlots, 236 implicitOnCore: true, 237 implicitOnClassic: true, 238 }, 239 }) 240 }