gitee.com/mysnapcore/mysnapd@v0.1.0/interfaces/builtin/posix_mq.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2022 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/apparmor" 30 "gitee.com/mysnapcore/mysnapd/interfaces/seccomp" 31 "gitee.com/mysnapcore/mysnapd/metautil" 32 apparmor_sandbox "gitee.com/mysnapcore/mysnapd/sandbox/apparmor" 33 "gitee.com/mysnapcore/mysnapd/snap" 34 "gitee.com/mysnapcore/mysnapd/strutil" 35 ) 36 37 const posixMQSummary = `allows access to POSIX message queues` 38 39 // This interface is super-privileged 40 const posixMQBaseDeclarationSlots = ` 41 posix-mq: 42 allow-installation: false 43 deny-connection: true 44 deny-auto-connection: true 45 ` 46 47 // Paths can only be specified by the slot and a slot needs to exist for the 48 // plug to connect to, so the plug does not need to be marked super-privileged 49 const posixMQBaseDeclarationPlugs = ` 50 posix-mq: 51 allow-installation: true 52 allow-connection: 53 slot-attributes: 54 posix-mq: $PLUG(posix-mq) 55 allow-auto-connection: 56 slot-publisher-id: 57 - $PLUG_PUBLISHER_ID 58 slot-attributes: 59 posix-mq: $PLUG(posix-mq) 60 ` 61 62 const posixMQPermanentSlotSecComp = ` 63 mq_open 64 mq_getsetattr 65 mq_unlink 66 mq_notify 67 mq_timedreceive 68 mq_timedsend 69 ` 70 71 var posixMQPlugPermissions = []string{ 72 "open", 73 "read", 74 "write", 75 "create", 76 "delete", 77 } 78 79 var posixMQDefaultPlugPermissions = []string{ 80 "read", 81 "write", 82 } 83 84 // Ensure that the name matches the criteria from the mq_overview man page: 85 // Each message queue is identified by a name of the form /somename; 86 // that is, a null-terminated string of up to NAME_MAX (i.e., 255) 87 // characters consisting of an initial slash, followed by one or more 88 // characters, none of which are slashes. 89 var posixMQNamePattern = regexp.MustCompile(`^/[^/]{1,255}$`) 90 91 type posixMQInterface struct { 92 commonInterface 93 } 94 95 func (iface *posixMQInterface) StaticInfo() interfaces.StaticInfo { 96 return interfaces.StaticInfo{ 97 Summary: posixMQSummary, 98 BaseDeclarationSlots: posixMQBaseDeclarationSlots, 99 BaseDeclarationPlugs: posixMQBaseDeclarationPlugs, 100 } 101 } 102 103 func (iface *posixMQInterface) Name() string { 104 return "posix-mq" 105 } 106 107 func (iface *posixMQInterface) checkPosixMQAppArmorSupport() error { 108 if apparmor_sandbox.ProbedLevel() == apparmor_sandbox.Unsupported { 109 // AppArmor is not supported at all; no need to add rules 110 return nil 111 } 112 113 features, err := apparmor_sandbox.ParserFeatures() 114 if err != nil { 115 return err 116 } 117 118 if !strutil.ListContains(features, "mqueue") { 119 return fmt.Errorf("AppArmor does not support POSIX message queues - cannot setup or connect interfaces") 120 } 121 122 return nil 123 } 124 125 func (iface *posixMQInterface) validatePermissionList(perms []string, name string) error { 126 for _, perm := range perms { 127 if !strutil.ListContains(posixMQPlugPermissions, perm) { 128 return fmt.Errorf("posix-mq slot permission \"%s\" not valid, must be one of %v", perm, posixMQPlugPermissions) 129 } 130 } 131 132 return nil 133 } 134 135 func (iface *posixMQInterface) validatePermissionsAttr(permsAttr interface{}) ([]string, error) { 136 var perms []string 137 permsList, ok := permsAttr.([]interface{}) 138 139 if !ok { 140 return nil, fmt.Errorf(`posix-mq slot "permissions" attribute must be a list of strings, not %v`, permsAttr) 141 } 142 143 // Ensure that each permission in the list is a string 144 for _, i := range permsList { 145 perm, ok := i.(string) 146 if !ok { 147 return nil, fmt.Errorf(`each posix-mq slot permission must be a string, not %v`, permsAttr) 148 } 149 perms = append(perms, perm) 150 } 151 152 return perms, nil 153 } 154 155 func (iface *posixMQInterface) getPermissions(attrs interfaces.Attrer, name string) ([]string, error) { 156 var perms []string 157 158 err := attrs.Attr("permissions", &perms) 159 switch { 160 case errors.Is(err, snap.AttributeNotFoundError{}): 161 // If the permissions have not been specified, use the defaults 162 perms = posixMQDefaultPlugPermissions 163 case err != nil: 164 return nil, err 165 } 166 167 if err := iface.validatePermissionList(perms, name); err != nil { 168 return nil, err 169 } 170 171 return perms, nil 172 } 173 174 func (iface *posixMQInterface) getPaths(attrs interfaces.Attrer, name string) ([]string, error) { 175 var pathList []string 176 var pathStr string 177 178 // The path attribute can either be a string or an array of strings 179 err := attrs.Attr("path", &pathStr) 180 switch { 181 case errors.Is(err, snap.AttributeNotFoundError{}): 182 return nil, fmt.Errorf(`posix-mq slot requires the "path" attribute`) 183 case errors.Is(err, metautil.AttributeNotCompatibleError{}): 184 // If the attribute exists but reading it as a string didn't work, try reading it as an array 185 if err = attrs.Attr("path", &pathList); err != nil { 186 // If that didn't work, the attribute is an invalid type 187 return nil, err 188 } 189 case err != nil: 190 return nil, err 191 default: 192 // If the path is a single string, turn it into an array 193 pathList = append(pathList, pathStr) 194 } 195 196 if len(pathList) == 0 { 197 return nil, fmt.Errorf(`posix-mq slot requires at least one value in the "path" attribute`) 198 } 199 200 for i, path := range pathList { 201 if len(path) == 0 { 202 return nil, fmt.Errorf(`posix-mq slot "path" attribute values cannot be empty`) 203 } 204 205 // Path must begin with a / 206 if path[0] != '/' { 207 path = "/" + path 208 pathList[i] = path 209 } 210 211 if err := iface.validatePath(name, path); err != nil { 212 return nil, err 213 } 214 } 215 216 return pathList, nil 217 } 218 219 func (iface *posixMQInterface) validatePath(name, path string) error { 220 if !posixMQNamePattern.MatchString(path) { 221 return fmt.Errorf(`posix-mq "path" attribute must conform to the POSIX message queue name specifications (see "man mq_overview"): %v`, path) 222 } 223 224 if err := apparmor_sandbox.ValidateNoAppArmorRegexp(path); err != nil { 225 return fmt.Errorf(`posix-mq "path" attribute is invalid: %v"`, path) 226 } 227 228 if !cleanSubPath(path) { 229 return fmt.Errorf(`posix-mq "path" attribute is not a clean path: %q`, path) 230 } 231 232 return nil 233 } 234 235 func (iface *posixMQInterface) checkPosixMQAttr(name string, attrs *map[string]interface{}) error { 236 posixMQAttr, isSet := (*attrs)["posix-mq"] 237 posixMQ, ok := posixMQAttr.(string) 238 if isSet && !ok { 239 return fmt.Errorf(`posix-mq "posix-mq" attribute must be a string, not %v`, (*attrs)["posix-mq"]) 240 } 241 if posixMQ == "" { 242 if *attrs == nil { 243 *attrs = make(map[string]interface{}) 244 } 245 // posix-mq attribute defaults to name if unspecified 246 (*attrs)["posix-mq"] = name 247 } 248 249 return nil 250 } 251 252 func (iface *posixMQInterface) BeforePreparePlug(plug *snap.PlugInfo) error { 253 if err := iface.checkPosixMQAppArmorSupport(); err != nil { 254 return err 255 } 256 257 if err := iface.checkPosixMQAttr(plug.Name, &plug.Attrs); err != nil { 258 return err 259 } 260 261 // Plugs don't have any path or permission arguments to validate; 262 // everything is configured by the slot 263 264 return nil 265 } 266 267 func (iface *posixMQInterface) BeforePrepareSlot(slot *snap.SlotInfo) error { 268 if err := iface.checkPosixMQAppArmorSupport(); err != nil { 269 return err 270 } 271 272 if err := iface.checkPosixMQAttr(slot.Name, &slot.Attrs); err != nil { 273 return err 274 } 275 276 // Only ensure that the given permissions are valid, don't use them here 277 if _, err := iface.getPermissions(slot, slot.Name); err != nil { 278 return err 279 } 280 281 // Only ensure that the given path is valid, don't use it here 282 if _, err := iface.getPaths(slot, slot.Name); err != nil { 283 return err 284 } 285 286 return nil 287 } 288 289 func (iface *posixMQInterface) generateSnippet(name, plugOrSlot string, permissions, paths []string) string { 290 var snippet strings.Builder 291 aaPerms := strings.Join(permissions, " ") 292 293 snippet.WriteString(fmt.Sprintf(" # POSIX Message Queue %s: %s\n", plugOrSlot, name)) 294 for _, path := range paths { 295 snippet.WriteString(fmt.Sprintf(" mqueue (%s) \"%s\",\n", aaPerms, path)) 296 } 297 298 return snippet.String() 299 } 300 301 func (iface *posixMQInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { 302 if implicitSystemPermanentSlot(slot) { 303 return nil 304 } 305 306 paths, err := iface.getPaths(slot, slot.Name) 307 if err != nil { 308 return err 309 } 310 311 // Slots always have all permissions enabled for the given message queue path 312 snippet := iface.generateSnippet(slot.Name, "slot", posixMQPlugPermissions, paths) 313 spec.AddSnippet(snippet) 314 315 return nil 316 } 317 318 func (iface *posixMQInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { 319 paths, err := iface.getPaths(slot, slot.Name()) 320 if err != nil { 321 return err 322 } 323 324 perms, err := iface.getPermissions(slot, slot.Name()) 325 if err != nil { 326 return err 327 } 328 329 // Always allow "open" 330 if !strutil.ListContains(perms, "open") { 331 perms = append(perms, "open") 332 } 333 334 snippet := iface.generateSnippet(plug.Name(), "plug", perms, paths) 335 spec.AddSnippet(snippet) 336 337 return nil 338 } 339 340 func (iface *posixMQInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *snap.SlotInfo) error { 341 spec.AddSnippet(posixMQPermanentSlotSecComp) 342 return nil 343 } 344 345 func (iface *posixMQInterface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { 346 perms, err := iface.getPermissions(slot, slot.Name()) 347 if err != nil { 348 return err 349 } 350 351 var syscalls = []string{ 352 // Always allow these functions 353 "mq_open", 354 "mq_getsetattr", 355 } 356 357 for _, perm := range perms { 358 // Only these permissions have associated syscalls 359 switch perm { 360 case "read": 361 syscalls = append(syscalls, "mq_timedreceive") 362 syscalls = append(syscalls, "mq_notify") 363 case "write": 364 syscalls = append(syscalls, "mq_timedsend") 365 case "delete": 366 syscalls = append(syscalls, "mq_unlink") 367 } 368 } 369 spec.AddSnippet(strings.Join(syscalls, "\n")) 370 371 return nil 372 } 373 374 func init() { 375 registerIface(&posixMQInterface{}) 376 }