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  }