gitee.com/mysnapcore/mysnapd@v0.1.0/interfaces/builtin/kernel_module_load.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2021 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/kmod"
    30  	"gitee.com/mysnapcore/mysnapd/snap"
    31  )
    32  
    33  const kernelModuleLoadSummary = `allows constrained control over kernel module loading`
    34  
    35  const kernelModuleLoadBaseDeclarationPlugs = `
    36    kernel-module-load:
    37      allow-installation: false
    38      deny-auto-connection: true
    39  `
    40  
    41  const kernelModuleLoadBaseDeclarationSlots = `
    42    kernel-module-load:
    43      allow-installation:
    44        slot-snap-type:
    45          - core
    46      deny-connection: true
    47  `
    48  
    49  var modulesAttrTypeError = errors.New(`kernel-module-load "modules" attribute must be a list of dictionaries`)
    50  
    51  // kernelModuleLoadInterface allows creating transient and persistent modules
    52  type kernelModuleLoadInterface struct {
    53  	commonInterface
    54  }
    55  
    56  type loadOption int
    57  
    58  const (
    59  	loadNone loadOption = iota
    60  	loadDenied
    61  	loadOnBoot
    62  	loadDynamic
    63  )
    64  
    65  type ModuleInfo struct {
    66  	name    string
    67  	load    loadOption
    68  	options string
    69  }
    70  
    71  var kernelModuleNameRegexp = regexp.MustCompile(`^[-a-zA-Z0-9_]+$`)
    72  var kernelModuleOptionsRegexp = regexp.MustCompile(`^([a-zA-Z][a-zA-Z0-9_]*(=[[:graph:]]+)? *)+$`)
    73  
    74  func enumerateModules(plug interfaces.Attrer, handleModule func(moduleInfo *ModuleInfo) error) error {
    75  	var modules []map[string]interface{}
    76  	err := plug.Attr("modules", &modules)
    77  	if err != nil && !errors.Is(err, snap.AttributeNotFoundError{}) {
    78  		return modulesAttrTypeError
    79  	}
    80  
    81  	for _, module := range modules {
    82  		name, ok := module["name"].(string)
    83  		if !ok {
    84  			return errors.New(`kernel-module-load "name" must be a string`)
    85  		}
    86  
    87  		var load loadOption
    88  		if loadAttr, found := module["load"]; found {
    89  			loadString, ok := loadAttr.(string)
    90  			if !ok {
    91  				return errors.New(`kernel-module-load "load" must be a string`)
    92  			}
    93  
    94  			switch loadString {
    95  			case "denied":
    96  				load = loadDenied
    97  			case "on-boot":
    98  				load = loadOnBoot
    99  			case "dynamic":
   100  				load = loadDynamic
   101  			default:
   102  				return fmt.Errorf(`kernel-module-load "load" value is unrecognized: %q`, loadString)
   103  			}
   104  		}
   105  
   106  		var options string
   107  		if optionsAttr, found := module["options"]; found {
   108  			options, ok = optionsAttr.(string)
   109  			if !ok {
   110  				return errors.New(`kernel-module-load "options" must be a string`)
   111  			}
   112  		}
   113  
   114  		moduleInfo := &ModuleInfo{
   115  			name:    name,
   116  			load:    load,
   117  			options: options,
   118  		}
   119  
   120  		if err := handleModule(moduleInfo); err != nil {
   121  			return err
   122  		}
   123  	}
   124  
   125  	return nil
   126  }
   127  
   128  func validateNameAttr(name string) error {
   129  	if !kernelModuleNameRegexp.MatchString(name) {
   130  		return errors.New(`kernel-module-load "name" attribute is not a valid module name`)
   131  	}
   132  
   133  	return nil
   134  }
   135  
   136  func validateOptionsAttr(moduleInfo *ModuleInfo) error {
   137  	if moduleInfo.options == "" {
   138  		return nil
   139  	}
   140  
   141  	if moduleInfo.load == loadDenied {
   142  		return errors.New(`kernel-module-load "options" attribute incompatible with "load: denied"`)
   143  	}
   144  
   145  	dynamicLoadingWithAnyOptions := moduleInfo.load == loadDynamic && moduleInfo.options == "*"
   146  	if !dynamicLoadingWithAnyOptions && !kernelModuleOptionsRegexp.MatchString(moduleInfo.options) {
   147  		return fmt.Errorf(`kernel-module-load "options" attribute contains invalid characters: %q`, moduleInfo.options)
   148  	}
   149  
   150  	return nil
   151  }
   152  
   153  func validateModuleInfo(moduleInfo *ModuleInfo) error {
   154  	if err := validateNameAttr(moduleInfo.name); err != nil {
   155  		return err
   156  	}
   157  
   158  	if err := validateOptionsAttr(moduleInfo); err != nil {
   159  		return err
   160  	}
   161  
   162  	if moduleInfo.options == "" && moduleInfo.load == loadNone {
   163  		return errors.New(`kernel-module-load: must specify at least "load" or "options"`)
   164  	}
   165  
   166  	return nil
   167  }
   168  
   169  func (iface *kernelModuleLoadInterface) BeforeConnectPlug(plug *interfaces.ConnectedPlug) error {
   170  	numModulesEntries := 0
   171  	err := enumerateModules(plug, func(moduleInfo *ModuleInfo) error {
   172  		numModulesEntries++
   173  		return validateModuleInfo(moduleInfo)
   174  	})
   175  	if err != nil {
   176  		return err
   177  	}
   178  
   179  	if numModulesEntries == 0 {
   180  		return modulesAttrTypeError
   181  	}
   182  
   183  	return nil
   184  }
   185  
   186  func (iface *kernelModuleLoadInterface) KModConnectedPlug(spec *kmod.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
   187  	snapInfo := plug.Snap()
   188  	commonDataDir := snapInfo.CommonDataDir()
   189  
   190  	err := enumerateModules(plug, func(moduleInfo *ModuleInfo) error {
   191  		var err error
   192  		switch moduleInfo.load {
   193  		case loadDenied:
   194  			err = spec.DisallowModule(moduleInfo.name)
   195  		case loadOnBoot:
   196  			err = spec.AddModule(moduleInfo.name)
   197  			if err != nil {
   198  				break
   199  			}
   200  			fallthrough
   201  		case loadNone, loadDynamic:
   202  			if len(moduleInfo.options) > 0 && moduleInfo.options != "*" {
   203  				// module options might include filesystem paths. Beside
   204  				// supporting hardcoded paths, it makes sense to support also
   205  				// paths to files provided by the snap; for this reason, we
   206  				// support expanding the $SNAP_COMMON variable here.
   207  				// We do not use os.Expand() because that supports both $ENV
   208  				// and ${ENV}, and we'd rather not alter the options which
   209  				// contain a "$" but are not meant to be expanded. Instead,
   210  				// just look for the "$SNAP_COMMON/" string and replace it; the
   211  				// extra "/" at the end ensures that the variable is
   212  				// terminated.
   213  				options := strings.ReplaceAll(moduleInfo.options, "$SNAP_COMMON/", commonDataDir+"/")
   214  				err = spec.SetModuleOptions(moduleInfo.name, options)
   215  			}
   216  		default:
   217  			// we can panic, this will be catched on validation
   218  			panic("Unsupported module load option")
   219  		}
   220  		return err
   221  	})
   222  	return err
   223  }
   224  
   225  func (iface *kernelModuleLoadInterface) AutoConnect(*snap.PlugInfo, *snap.SlotInfo) bool {
   226  	return true
   227  }
   228  
   229  func init() {
   230  	registerIface(&kernelModuleLoadInterface{
   231  		commonInterface: commonInterface{
   232  			name:                 "kernel-module-load",
   233  			summary:              kernelModuleLoadSummary,
   234  			baseDeclarationPlugs: kernelModuleLoadBaseDeclarationPlugs,
   235  			baseDeclarationSlots: kernelModuleLoadBaseDeclarationSlots,
   236  			implicitOnCore:       true,
   237  			implicitOnClassic:    true,
   238  		},
   239  	})
   240  }