gitee.com/mysnapcore/mysnapd@v0.1.0/interfaces/builtin/dbus.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016 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 "bytes" 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/dbus" 31 "gitee.com/mysnapcore/mysnapd/interfaces/seccomp" 32 "gitee.com/mysnapcore/mysnapd/release" 33 "gitee.com/mysnapcore/mysnapd/snap" 34 ) 35 36 const dbusSummary = `allows owning a specific name on DBus` 37 38 const dbusBaseDeclarationSlots = ` 39 dbus: 40 allow-installation: 41 slot-snap-type: 42 - app 43 deny-connection: 44 slot-attributes: 45 name: .+ 46 deny-auto-connection: true 47 ` 48 49 const dbusPermanentSlotAppArmor = ` 50 # Description: Allow owning a name on DBus public bus 51 52 #include <abstractions/###DBUS_ABSTRACTION###> 53 54 # register on DBus 55 dbus (send) 56 bus=###DBUS_BUS### 57 path=/org/freedesktop/DBus 58 interface=org.freedesktop.DBus 59 member="{Request,Release}Name" 60 peer=(name=org.freedesktop.DBus, label=unconfined), 61 62 dbus (send) 63 bus=###DBUS_BUS### 64 path=/org/freedesktop/DBus 65 interface=org.freedesktop.DBus 66 member="GetConnectionUnix{ProcessID,User}" 67 peer=(name=org.freedesktop.DBus, label=unconfined), 68 69 dbus (send) 70 bus=###DBUS_BUS### 71 path=/org/freedesktop/DBus 72 interface=org.freedesktop.DBus 73 member="GetConnectionCredentials" 74 peer=(name=org.freedesktop.DBus, label=unconfined), 75 76 # bind to a well-known DBus name: ###DBUS_NAME### 77 dbus (bind) 78 bus=###DBUS_BUS### 79 name=###DBUS_NAME###, 80 81 # For KDE applications and some other cases, also support alternation for: 82 # - using org.kde.foo-PID as the 'well-known' name 83 # - using org.foo.cmd_<num>_<num> as the 'well-known' name 84 # Note, snapd does not allow declaring a 'well-known' name that ends with 85 # '-[0-9]+' or that contains '_'. Parallel installs of DBus services aren't 86 # supported at this time, but if they were, this could allow a parallel 87 # install'swell-known name to overlap with the normal install. 88 dbus (bind) 89 bus=###DBUS_BUS### 90 name=###DBUS_NAME###{_,-}[1-9]{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9]}{,_[1-9]{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9]}}, 91 92 # For Firefox, support using org.mozilla.firefox.<id> as the 'well-known' name 93 # where <id> is the base64-encoded profile name. 94 # See https://bugzilla.mozilla.org/1441894 for a discussion and details. 95 dbus (bind) 96 bus=###DBUS_BUS### 97 name="###DBUS_NAME###.*", 98 99 # Allow us to talk to dbus-daemon 100 dbus (receive) 101 bus=###DBUS_BUS### 102 path=###DBUS_PATH### 103 peer=(name=org.freedesktop.DBus, label=unconfined), 104 dbus (send) 105 bus=###DBUS_BUS### 106 path=###DBUS_PATH### 107 interface=org.freedesktop.DBus.Properties 108 peer=(name=org.freedesktop.DBus, label=unconfined), 109 110 # Allow us to introspect org.freedesktop.DBus (needed by pydbus) 111 dbus (send) 112 bus=###DBUS_BUS### 113 interface=org.freedesktop.DBus.Introspectable 114 member=Introspect 115 peer=(name=org.freedesktop.DBus, label=unconfined), 116 ` 117 118 const dbusPermanentSlotAppArmorClassic = ` 119 # allow unconfined clients to introspect us on classic 120 dbus (receive) 121 bus=###DBUS_BUS### 122 interface=org.freedesktop.DBus.Introspectable 123 member=Introspect 124 peer=(label=unconfined), 125 126 # allow us to respond to unconfined clients via ###DBUS_INTERFACE### 127 # on classic (send should be handled via another snappy interface). 128 dbus (receive) 129 bus=###DBUS_BUS### 130 interface=###DBUS_INTERFACE### 131 peer=(label=unconfined), 132 133 # allow us to respond to unconfined clients via ###DBUS_PATH### (eg, 134 # org.freedesktop.*, org.gtk.Application, etc) on classic (send should be 135 # handled via another snappy interface). 136 dbus (receive) 137 bus=###DBUS_BUS### 138 path=###DBUS_PATH### 139 peer=(label=unconfined), 140 ` 141 142 const dbusPermanentSlotDBus = ` 143 <policy user="root"> 144 <allow own="###DBUS_NAME###"/> 145 <allow send_destination="###DBUS_NAME###"/> 146 </policy> 147 <policy context="default"> 148 <allow send_destination="###DBUS_NAME###"/> 149 </policy> 150 ` 151 152 const dbusPermanentSlotSecComp = ` 153 # Description: Allow owning a name and listening on DBus public bus 154 listen 155 accept 156 accept4 157 ` 158 159 const dbusConnectedSlotAppArmor = ` 160 # allow snaps to introspect us. This allows clients to introspect all 161 # DBus interfaces of this service (but not use them). 162 dbus (receive) 163 bus=###DBUS_BUS### 164 interface=org.freedesktop.DBus.Introspectable 165 member=Introspect 166 peer=(label=###PLUG_SECURITY_TAGS###), 167 168 # allow connected snaps to all paths via ###DBUS_INTERFACE### 169 dbus (receive, send) 170 bus=###DBUS_BUS### 171 interface=###DBUS_INTERFACE### 172 peer=(label=###PLUG_SECURITY_TAGS###), 173 174 # allow connected snaps to all interfaces via ###DBUS_PATH### (eg, 175 # org.freedesktop.*, org.gtk.Application, etc) to allow full integration with 176 # connected snaps. 177 dbus (receive, send) 178 bus=###DBUS_BUS### 179 path=###DBUS_PATH### 180 peer=(label=###PLUG_SECURITY_TAGS###), 181 ` 182 183 const dbusConnectedPlugAppArmor = ` 184 #include <abstractions/###DBUS_ABSTRACTION###> 185 186 # allow snaps to introspect the slot servive. This allows us to introspect 187 # all DBus interfaces of the service (but not use them). 188 dbus (send) 189 bus=###DBUS_BUS### 190 interface=org.freedesktop.DBus.Introspectable 191 member=Introspect 192 peer=(label=###SLOT_SECURITY_TAGS###), 193 194 # allow connected snaps to ###DBUS_NAME### 195 dbus (receive, send) 196 bus=###DBUS_BUS### 197 peer=(name=###DBUS_NAME###, label=###SLOT_SECURITY_TAGS###), 198 # For KDE applications, also support alternation since they use org.kde.foo-PID 199 # as their 'well-known' name. snapd does not allow ###DBUS_NAME### to end with 200 # '-[0-9]+', so this is ok. 201 dbus (receive, send) 202 bus=###DBUS_BUS### 203 peer=(name="###DBUS_NAME###-[1-9]{,[0-9]}{,[0-9]}{,[0-9]}{,[0-9]}{,[0-9]}", label=###SLOT_SECURITY_TAGS###), 204 205 # allow connected snaps to all paths via ###DBUS_INTERFACE### to allow full 206 # integration with connected snaps. 207 dbus (receive, send) 208 bus=###DBUS_BUS### 209 interface=###DBUS_INTERFACE### 210 peer=(label=###SLOT_SECURITY_TAGS###), 211 212 # allow connected snaps to all interfaces via ###DBUS_PATH### (eg, 213 # org.freedesktop.*, org.gtk.Application, etc) to allow full integration with 214 # connected snaps. 215 dbus (receive, send) 216 bus=###DBUS_BUS### 217 path=###DBUS_PATH### 218 peer=(label=###SLOT_SECURITY_TAGS###), 219 ` 220 221 type dbusInterface struct{} 222 223 func (iface *dbusInterface) Name() string { 224 return "dbus" 225 } 226 227 func (iface *dbusInterface) StaticInfo() interfaces.StaticInfo { 228 return interfaces.StaticInfo{ 229 Summary: dbusSummary, 230 BaseDeclarationSlots: dbusBaseDeclarationSlots, 231 } 232 } 233 234 // snapd has AppArmor rules (see above) allowing binds to busName-PID 235 // so to avoid overlap with different snaps (eg, busName running as PID 236 // 123 and busName-123), don't allow busName to end with -PID. If that 237 // rule is removed, this limitation can be lifted. 238 var isInvalidSnappyBusName = regexp.MustCompile("-[0-9]+$").MatchString 239 240 // Obtain yaml-specified bus well-known name 241 func (iface *dbusInterface) getAttribs(attribs interfaces.Attrer) (string, string, error) { 242 // bus attribute 243 var bus string 244 if err := attribs.Attr("bus", &bus); err != nil { 245 return "", "", fmt.Errorf("cannot find attribute 'bus'") 246 } 247 248 if bus != "session" && bus != "system" { 249 return "", "", fmt.Errorf("bus '%s' must be one of 'session' or 'system'", bus) 250 } 251 252 // name attribute 253 var name string 254 if err := attribs.Attr("name", &name); err != nil { 255 return "", "", fmt.Errorf("cannot find attribute 'name'") 256 } 257 258 err := interfaces.ValidateDBusBusName(name) 259 if err != nil { 260 return "", "", err 261 } 262 263 if isInvalidSnappyBusName(name) { 264 return "", "", fmt.Errorf("DBus bus name must not end with -NUMBER") 265 } 266 267 return bus, name, nil 268 } 269 270 // Determine AppArmor dbus abstraction to use based on bus 271 func getAppArmorAbstraction(bus string) (string, error) { 272 var abstraction string 273 if bus == "system" { 274 abstraction = "dbus-strict" 275 } else if bus == "session" { 276 abstraction = "dbus-session-strict" 277 } else { 278 return "", fmt.Errorf("unknown abstraction for specified bus '%q'", bus) 279 } 280 return abstraction, nil 281 } 282 283 // Calculate individual snippet policy based on bus and name 284 func getAppArmorSnippet(policy string, bus string, name string) string { 285 old := "###DBUS_BUS###" 286 new := bus 287 snippet := strings.Replace(policy, old, new, -1) 288 289 old = "###DBUS_NAME###" 290 new = name 291 snippet = strings.Replace(snippet, old, new, -1) 292 293 // convert name to AppArmor dbus path (eg 'org.foo' to '/org/foo{,/**}') 294 var pathBuf bytes.Buffer 295 pathBuf.WriteString(`"/`) 296 pathBuf.WriteString(strings.Replace(name, ".", "/", -1)) 297 pathBuf.WriteString(`{,/**}"`) 298 299 old = "###DBUS_PATH###" 300 new = pathBuf.String() 301 snippet = strings.Replace(snippet, old, new, -1) 302 303 // convert name to AppArmor dbus interface (eg, 'org.foo' to 'org.foo{,.*}') 304 var ifaceBuf bytes.Buffer 305 ifaceBuf.WriteString(`"`) 306 ifaceBuf.WriteString(name) 307 ifaceBuf.WriteString(`{,.*}"`) 308 309 old = "###DBUS_INTERFACE###" 310 new = ifaceBuf.String() 311 snippet = strings.Replace(snippet, old, new, -1) 312 313 return snippet 314 } 315 316 func (iface *dbusInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { 317 bus, name, err := iface.getAttribs(plug) 318 if err != nil { 319 return err 320 } 321 322 busSlot, nameSlot, err := iface.getAttribs(slot) 323 if err != nil { 324 return err 325 } 326 327 // ensure that we only connect to slot with matching attributes 328 if bus != busSlot || name != nameSlot { 329 return nil 330 } 331 332 // well-known DBus name-specific connected plug policy 333 snippet := getAppArmorSnippet(dbusConnectedPlugAppArmor, bus, name) 334 335 // abstraction policy 336 abstraction, err := getAppArmorAbstraction(bus) 337 if err != nil { 338 return err 339 } 340 341 old := "###DBUS_ABSTRACTION###" 342 new := abstraction 343 snippet = strings.Replace(snippet, old, new, -1) 344 345 old = "###SLOT_SECURITY_TAGS###" 346 new = slotAppLabelExpr(slot) 347 snippet = strings.Replace(snippet, old, new, -1) 348 349 spec.AddSnippet(snippet) 350 return nil 351 } 352 353 func (iface *dbusInterface) DBusPermanentSlot(spec *dbus.Specification, slot *snap.SlotInfo) error { 354 bus, name, err := iface.getAttribs(slot) 355 if err != nil { 356 return err 357 } 358 359 // only system services need bus policy 360 if bus != "system" { 361 return nil 362 } 363 364 old := "###DBUS_NAME###" 365 new := name 366 spec.AddSnippet(strings.Replace(dbusPermanentSlotDBus, old, new, -1)) 367 return nil 368 } 369 370 func (iface *dbusInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { 371 bus, name, err := iface.getAttribs(slot) 372 if err != nil { 373 return err 374 } 375 376 // well-known DBus name-specific permanent slot policy 377 snippet := getAppArmorSnippet(dbusPermanentSlotAppArmor, bus, name) 378 379 // abstraction policy 380 abstraction, err := getAppArmorAbstraction(bus) 381 if err != nil { 382 return err 383 } 384 385 old := "###DBUS_ABSTRACTION###" 386 new := abstraction 387 snippet = strings.Replace(snippet, old, new, -1) 388 spec.AddSnippet(snippet) 389 390 if release.OnClassic { 391 // classic-only policy 392 spec.AddSnippet(getAppArmorSnippet(dbusPermanentSlotAppArmorClassic, bus, name)) 393 } 394 return nil 395 } 396 397 func (iface *dbusInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *snap.SlotInfo) error { 398 spec.AddSnippet(dbusPermanentSlotSecComp) 399 return nil 400 } 401 402 func (iface *dbusInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { 403 bus, name, err := iface.getAttribs(slot) 404 if err != nil { 405 return err 406 } 407 408 busPlug, namePlug, err := iface.getAttribs(plug) 409 if err != nil { 410 return err 411 } 412 413 // ensure that we only connect to slot with matching attributes. This 414 // makes sure that the security policy is correct, but does not ensure 415 // that 'snap interfaces' is correct. 416 // TODO: we can fix the 'snap interfaces' issue when interface/policy 417 // checkers when they are available 418 if bus != busPlug || name != namePlug { 419 return nil 420 } 421 422 // well-known DBus name-specific connected slot policy 423 snippet := getAppArmorSnippet(dbusConnectedSlotAppArmor, bus, name) 424 425 old := "###PLUG_SECURITY_TAGS###" 426 new := plugAppLabelExpr(plug) 427 snippet = strings.Replace(snippet, old, new, -1) 428 spec.AddSnippet(snippet) 429 return nil 430 } 431 432 func (iface *dbusInterface) BeforePreparePlug(plug *snap.PlugInfo) error { 433 _, _, err := iface.getAttribs(plug) 434 return err 435 } 436 437 func (iface *dbusInterface) BeforePrepareSlot(slot *snap.SlotInfo) error { 438 _, _, err := iface.getAttribs(slot) 439 return err 440 } 441 442 func (iface *dbusInterface) AutoConnect(*snap.PlugInfo, *snap.SlotInfo) bool { 443 // allow what declarations allowed 444 return true 445 } 446 447 func init() { 448 registerIface(&dbusInterface{}) 449 }