github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/interfaces/builtin/bool_file.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  	"fmt"
    24  	"path/filepath"
    25  	"regexp"
    26  
    27  	"github.com/snapcore/snapd/interfaces"
    28  	"github.com/snapcore/snapd/interfaces/apparmor"
    29  	"github.com/snapcore/snapd/snap"
    30  )
    31  
    32  const boolFileSummary = `allows access to specific file with bool semantics`
    33  
    34  const boolFileBaseDeclarationSlots = `
    35    bool-file:
    36      allow-installation:
    37        slot-snap-type:
    38          - core
    39          - gadget
    40      deny-auto-connection: true
    41  `
    42  
    43  // boolFileInterface is the type of all the bool-file interfaces.
    44  type boolFileInterface struct{}
    45  
    46  // String returns the same value as Name().
    47  func (iface *boolFileInterface) String() string {
    48  	return iface.Name()
    49  }
    50  
    51  // Name returns the name of the bool-file interface.
    52  func (iface *boolFileInterface) Name() string {
    53  	return "bool-file"
    54  }
    55  
    56  func (iface *boolFileInterface) StaticInfo() interfaces.StaticInfo {
    57  	return interfaces.StaticInfo{
    58  		Summary:              boolFileSummary,
    59  		BaseDeclarationSlots: boolFileBaseDeclarationSlots,
    60  	}
    61  }
    62  
    63  var boolFileGPIOValuePattern = regexp.MustCompile(
    64  	"^/sys/class/gpio/gpio[0-9]+/value$")
    65  var boolFileAllowedPathPatterns = []*regexp.Regexp{
    66  	// The brightness of standard LED class device
    67  	regexp.MustCompile("^/sys/class/leds/[^/]+/brightness$"),
    68  	// The value of standard exported GPIO
    69  	boolFileGPIOValuePattern,
    70  }
    71  
    72  // BeforePrepareSlot checks and possibly modifies a slot.
    73  // Valid "bool-file" slots must contain the attribute "path".
    74  func (iface *boolFileInterface) BeforePrepareSlot(slot *snap.SlotInfo) error {
    75  	path, ok := slot.Attrs["path"].(string)
    76  	if !ok || path == "" {
    77  		return fmt.Errorf("bool-file must contain the path attribute")
    78  	}
    79  	// XXX: this interface feeds the cleaned path into the regex and is
    80  	// left unchanged here for historical reasons. New interfaces (eg,
    81  	// like raw-volume) should instead use verifySlotPathAttribute() which
    82  	// performs additional verification.
    83  	path = filepath.Clean(path)
    84  	for _, pattern := range boolFileAllowedPathPatterns {
    85  		if pattern.MatchString(path) {
    86  			return nil
    87  		}
    88  	}
    89  	return fmt.Errorf("bool-file can only point at LED brightness or GPIO value")
    90  }
    91  
    92  func (iface *boolFileInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error {
    93  	gpioSnippet := `
    94  /sys/class/gpio/export rw,
    95  /sys/class/gpio/unexport rw,
    96  /sys/class/gpio/gpio[0-9]+/direction rw,
    97  `
    98  
    99  	if iface.isGPIO(slot) {
   100  		spec.AddSnippet(gpioSnippet)
   101  	}
   102  	return nil
   103  }
   104  
   105  func (iface *boolFileInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
   106  	// Allow write and lock on the file designated by the path.
   107  	// Dereference symbolic links to file path handed out to apparmor since
   108  	// sysfs is full of symlinks and apparmor requires uses real path for
   109  	// filtering.
   110  	path, err := iface.dereferencedPath(slot)
   111  	if err != nil {
   112  		return fmt.Errorf("cannot compute plug security snippet: %v", err)
   113  	}
   114  	spec.AddSnippet(fmt.Sprintf("%s rwk,", path))
   115  	return nil
   116  }
   117  
   118  func (iface *boolFileInterface) dereferencedPath(slot *interfaces.ConnectedSlot) (string, error) {
   119  	var path string
   120  	if err := slot.Attr("path", &path); err == nil {
   121  		path, err = evalSymlinks(path)
   122  		if err != nil {
   123  			return "", err
   124  		}
   125  		return filepath.Clean(path), nil
   126  	}
   127  	panic("slot is not sanitized")
   128  }
   129  
   130  // isGPIO checks if a given bool-file slot refers to a GPIO pin.
   131  func (iface *boolFileInterface) isGPIO(slot *snap.SlotInfo) bool {
   132  	var path string
   133  	if err := slot.Attr("path", &path); err == nil {
   134  		path = filepath.Clean(path)
   135  		return boolFileGPIOValuePattern.MatchString(path)
   136  	}
   137  	panic("slot is not sanitized")
   138  }
   139  
   140  // AutoConnect returns whether plug and slot should be implicitly
   141  // auto-connected assuming they will be an unambiguous connection
   142  // candidate and declaration-based checks allow.
   143  //
   144  // By default we allow what declarations allowed.
   145  func (iface *boolFileInterface) AutoConnect(*snap.PlugInfo, *snap.SlotInfo) bool {
   146  	return true
   147  }
   148  
   149  func init() {
   150  	registerIface(&boolFileInterface{})
   151  }