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