github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/boot/cmdline_test.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 boot_test
    21  
    22  import (
    23  	"io/ioutil"
    24  	"os"
    25  	"path/filepath"
    26  
    27  	. "gopkg.in/check.v1"
    28  
    29  	"github.com/snapcore/snapd/boot"
    30  	"github.com/snapcore/snapd/boot/boottest"
    31  	"github.com/snapcore/snapd/bootloader"
    32  	"github.com/snapcore/snapd/bootloader/bootloadertest"
    33  	"github.com/snapcore/snapd/osutil"
    34  	"github.com/snapcore/snapd/snap/snaptest"
    35  	"github.com/snapcore/snapd/testutil"
    36  )
    37  
    38  var _ = Suite(&kernelCommandLineSuite{})
    39  
    40  // baseBootSuite is used to setup the common test environment
    41  type kernelCommandLineSuite struct {
    42  	testutil.BaseTest
    43  	rootDir string
    44  }
    45  
    46  func (s *kernelCommandLineSuite) SetUpTest(c *C) {
    47  	s.BaseTest.SetUpTest(c)
    48  	s.rootDir = c.MkDir()
    49  
    50  	err := os.MkdirAll(filepath.Join(s.rootDir, "proc"), 0755)
    51  	c.Assert(err, IsNil)
    52  	restore := osutil.MockProcCmdline(filepath.Join(s.rootDir, "proc/cmdline"))
    53  	s.AddCleanup(restore)
    54  }
    55  
    56  func (s *kernelCommandLineSuite) mockProcCmdlineContent(c *C, newContent string) {
    57  	mockProcCmdline := filepath.Join(s.rootDir, "proc/cmdline")
    58  	err := ioutil.WriteFile(mockProcCmdline, []byte(newContent), 0644)
    59  	c.Assert(err, IsNil)
    60  }
    61  
    62  func (s *kernelCommandLineSuite) TestModeAndLabel(c *C) {
    63  	for _, tc := range []struct {
    64  		cmd   string
    65  		mode  string
    66  		label string
    67  		err   string
    68  	}{{
    69  		cmd:   "snapd_recovery_mode= snapd_recovery_system=this-is-a-label other-option=foo",
    70  		mode:  boot.ModeInstall,
    71  		label: "this-is-a-label",
    72  	}, {
    73  		cmd:   "snapd_recovery_system=label foo=bar foobaz=\\0\\0123 snapd_recovery_mode=install",
    74  		label: "label",
    75  		mode:  boot.ModeInstall,
    76  	}, {
    77  		cmd:  "snapd_recovery_mode=run snapd_recovery_system=1234",
    78  		mode: boot.ModeRun,
    79  	}, {
    80  		cmd: "option=1 other-option=\0123 none",
    81  		err: "cannot detect mode nor recovery system to use",
    82  	}, {
    83  		cmd: "snapd_recovery_mode=install-foo",
    84  		err: `cannot use unknown mode "install-foo"`,
    85  	}, {
    86  		// no recovery system label
    87  		cmd: "snapd_recovery_mode=install foo=bar",
    88  		err: `cannot specify install mode without system label`,
    89  	}, {
    90  		cmd: "snapd_recovery_system=1234",
    91  		err: `cannot specify system label without a mode`,
    92  	}, {
    93  		// multiple kernel command line params end up using the last one - this
    94  		// effectively matches the kernel handling too
    95  		cmd:  "snapd_recovery_mode=install snapd_recovery_system=1234 snapd_recovery_mode=run",
    96  		mode: "run",
    97  		// label gets unset because it's not used for run mode
    98  		label: "",
    99  	}, {
   100  		cmd:   "snapd_recovery_system=not-this-one snapd_recovery_mode=install snapd_recovery_system=1234",
   101  		mode:  "install",
   102  		label: "1234",
   103  	}} {
   104  		c.Logf("tc: %q", tc)
   105  		s.mockProcCmdlineContent(c, tc.cmd)
   106  
   107  		mode, label, err := boot.ModeAndRecoverySystemFromKernelCommandLine()
   108  		if tc.err == "" {
   109  			c.Assert(err, IsNil)
   110  			c.Check(mode, Equals, tc.mode)
   111  			c.Check(label, Equals, tc.label)
   112  		} else {
   113  			c.Assert(err, ErrorMatches, tc.err)
   114  		}
   115  	}
   116  }
   117  
   118  func (s *kernelCommandLineSuite) TestComposeCommandLineNotManagedHappy(c *C) {
   119  	model := boottest.MakeMockUC20Model()
   120  
   121  	bl := bootloadertest.Mock("btloader", c.MkDir())
   122  	bootloader.Force(bl)
   123  	defer bootloader.Force(nil)
   124  
   125  	cmdline, err := boot.ComposeRecoveryCommandLine(model, "20200314", "")
   126  	c.Assert(err, IsNil)
   127  	c.Assert(cmdline, Equals, "")
   128  
   129  	cmdline, err = boot.ComposeCommandLine(model, "")
   130  	c.Assert(err, IsNil)
   131  	c.Assert(cmdline, Equals, "")
   132  
   133  	tbl := bl.WithTrustedAssets()
   134  	bootloader.Force(tbl)
   135  
   136  	cmdline, err = boot.ComposeRecoveryCommandLine(model, "20200314", "")
   137  	c.Assert(err, IsNil)
   138  	c.Assert(cmdline, Equals, "snapd_recovery_mode=recover snapd_recovery_system=20200314")
   139  
   140  	cmdline, err = boot.ComposeCommandLine(model, "")
   141  	c.Assert(err, IsNil)
   142  	c.Assert(cmdline, Equals, "snapd_recovery_mode=run")
   143  }
   144  
   145  func (s *kernelCommandLineSuite) TestComposeCommandLineNotUC20(c *C) {
   146  	model := boottest.MakeMockModel()
   147  
   148  	bl := bootloadertest.Mock("btloader", c.MkDir())
   149  	bootloader.Force(bl)
   150  	defer bootloader.Force(nil)
   151  	cmdline, err := boot.ComposeRecoveryCommandLine(model, "20200314", "")
   152  	c.Assert(err, IsNil)
   153  	c.Check(cmdline, Equals, "")
   154  
   155  	cmdline, err = boot.ComposeCommandLine(model, "")
   156  	c.Assert(err, IsNil)
   157  	c.Check(cmdline, Equals, "")
   158  }
   159  
   160  func (s *kernelCommandLineSuite) TestComposeCommandLineManagedHappy(c *C) {
   161  	model := boottest.MakeMockUC20Model()
   162  
   163  	tbl := bootloadertest.Mock("btloader", c.MkDir()).WithTrustedAssets()
   164  	bootloader.Force(tbl)
   165  	defer bootloader.Force(nil)
   166  
   167  	tbl.StaticCommandLine = "panic=-1"
   168  
   169  	cmdline, err := boot.ComposeRecoveryCommandLine(model, "20200314", "")
   170  	c.Assert(err, IsNil)
   171  	c.Assert(cmdline, Equals, "snapd_recovery_mode=recover snapd_recovery_system=20200314 panic=-1")
   172  	cmdline, err = boot.ComposeCommandLine(model, "")
   173  	c.Assert(err, IsNil)
   174  	c.Assert(cmdline, Equals, "snapd_recovery_mode=run panic=-1")
   175  
   176  	cmdline, err = boot.ComposeRecoveryCommandLine(model, "20200314", "")
   177  	c.Assert(err, IsNil)
   178  	c.Assert(cmdline, Equals, "snapd_recovery_mode=recover snapd_recovery_system=20200314 panic=-1")
   179  	cmdline, err = boot.ComposeCommandLine(model, "")
   180  	c.Assert(err, IsNil)
   181  	c.Assert(cmdline, Equals, "snapd_recovery_mode=run panic=-1")
   182  }
   183  
   184  func (s *kernelCommandLineSuite) TestComposeCandidateCommandLineManagedHappy(c *C) {
   185  	model := boottest.MakeMockUC20Model()
   186  
   187  	tbl := bootloadertest.Mock("btloader", c.MkDir()).WithTrustedAssets()
   188  	bootloader.Force(tbl)
   189  	defer bootloader.Force(nil)
   190  
   191  	tbl.StaticCommandLine = "panic=-1"
   192  	tbl.CandidateStaticCommandLine = "candidate panic=0"
   193  
   194  	cmdline, err := boot.ComposeCandidateCommandLine(model, "")
   195  	c.Assert(err, IsNil)
   196  	c.Assert(cmdline, Equals, "snapd_recovery_mode=run candidate panic=0")
   197  }
   198  
   199  func (s *kernelCommandLineSuite) TestComposeCandidateRecoveryCommandLineManagedHappy(c *C) {
   200  	model := boottest.MakeMockUC20Model()
   201  
   202  	tbl := bootloadertest.Mock("btloader", c.MkDir()).WithTrustedAssets()
   203  	bootloader.Force(tbl)
   204  	defer bootloader.Force(nil)
   205  
   206  	tbl.StaticCommandLine = "panic=-1"
   207  	tbl.CandidateStaticCommandLine = "candidate panic=0"
   208  
   209  	cmdline, err := boot.ComposeCandidateRecoveryCommandLine(model, "1234", "")
   210  	c.Assert(err, IsNil)
   211  	c.Check(cmdline, Equals, "snapd_recovery_mode=recover snapd_recovery_system=1234 candidate panic=0")
   212  
   213  	cmdline, err = boot.ComposeCandidateRecoveryCommandLine(model, "", "")
   214  	c.Assert(err, ErrorMatches, "internal error: system is unset")
   215  	c.Check(cmdline, Equals, "")
   216  }
   217  
   218  const gadgetSnapYaml = `name: gadget
   219  version: 1.0
   220  type: gadget
   221  `
   222  
   223  func (s *kernelCommandLineSuite) TestComposeCommandLineWithGadget(c *C) {
   224  	model := boottest.MakeMockUC20Model()
   225  
   226  	tbl := bootloadertest.Mock("btloader", c.MkDir()).WithTrustedAssets()
   227  	bootloader.Force(tbl)
   228  	defer bootloader.Force(nil)
   229  
   230  	tbl.StaticCommandLine = "panic=-1"
   231  	tbl.CandidateStaticCommandLine = "candidate panic=0"
   232  
   233  	for _, tc := range []struct {
   234  		which          string
   235  		files          [][]string
   236  		expCommandLine string
   237  		errMsg         string
   238  	}{{
   239  		which: "current",
   240  		files: [][]string{
   241  			{"cmdline.extra", "cmdline extra"},
   242  		},
   243  		expCommandLine: "snapd_recovery_mode=run panic=-1 cmdline extra",
   244  	}, {
   245  		which: "candidate",
   246  		files: [][]string{
   247  			{"cmdline.extra", "cmdline extra"},
   248  		},
   249  		expCommandLine: "snapd_recovery_mode=run candidate panic=0 cmdline extra",
   250  	}, {
   251  		which: "current",
   252  		files: [][]string{
   253  			{"cmdline.full", "cmdline full"},
   254  		},
   255  		expCommandLine: "snapd_recovery_mode=run cmdline full",
   256  	}, {
   257  		which: "candidate",
   258  		files: [][]string{
   259  			{"cmdline.full", "cmdline full"},
   260  		},
   261  		expCommandLine: "snapd_recovery_mode=run cmdline full",
   262  	}, {
   263  		which: "candidate",
   264  		files: [][]string{
   265  			{"cmdline.extra", `bad-quote="`},
   266  		},
   267  		errMsg: `cannot use kernel command line from gadget: invalid kernel command line in cmdline.extra: unbalanced quoting`,
   268  	}} {
   269  		sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, append([][]string{
   270  			{"meta/snap.yaml", gadgetSnapYaml},
   271  		}, tc.files...))
   272  		var cmdline string
   273  		var err error
   274  		switch tc.which {
   275  		case "current":
   276  			cmdline, err = boot.ComposeCommandLine(model, sf)
   277  		case "candidate":
   278  			cmdline, err = boot.ComposeCandidateCommandLine(model, sf)
   279  		default:
   280  			c.Fatalf("unexpected command line type")
   281  		}
   282  		if tc.errMsg == "" {
   283  			c.Assert(err, IsNil)
   284  			c.Assert(cmdline, Equals, tc.expCommandLine)
   285  		} else {
   286  			c.Assert(err, ErrorMatches, tc.errMsg)
   287  		}
   288  	}
   289  }
   290  
   291  func (s *kernelCommandLineSuite) TestComposeRecoveryCommandLineWithGadget(c *C) {
   292  	model := boottest.MakeMockUC20Model()
   293  
   294  	tbl := bootloadertest.Mock("btloader", c.MkDir()).WithTrustedAssets()
   295  	bootloader.Force(tbl)
   296  	defer bootloader.Force(nil)
   297  
   298  	tbl.StaticCommandLine = "panic=-1"
   299  	tbl.CandidateStaticCommandLine = "candidate panic=0"
   300  	system := "1234"
   301  
   302  	for _, tc := range []struct {
   303  		which          string
   304  		files          [][]string
   305  		expCommandLine string
   306  		errMsg         string
   307  	}{{
   308  		which: "current",
   309  		files: [][]string{
   310  			{"cmdline.extra", "cmdline extra"},
   311  		},
   312  		expCommandLine: "snapd_recovery_mode=recover snapd_recovery_system=1234 panic=-1 cmdline extra",
   313  	}, {
   314  		which: "candidate",
   315  		files: [][]string{
   316  			{"cmdline.extra", "cmdline extra"},
   317  		},
   318  		expCommandLine: "snapd_recovery_mode=recover snapd_recovery_system=1234 candidate panic=0 cmdline extra",
   319  	}, {
   320  		which: "current",
   321  		files: [][]string{
   322  			{"cmdline.full", "cmdline full"},
   323  		},
   324  		expCommandLine: "snapd_recovery_mode=recover snapd_recovery_system=1234 cmdline full",
   325  	}, {
   326  		which: "candidate",
   327  		files: [][]string{
   328  			{"cmdline.full", "cmdline full"},
   329  		},
   330  		expCommandLine: "snapd_recovery_mode=recover snapd_recovery_system=1234 cmdline full",
   331  	}, {
   332  		which: "candidate",
   333  		files: [][]string{
   334  			{"cmdline.extra", `bad-quote="`},
   335  		},
   336  		errMsg: `cannot use kernel command line from gadget: invalid kernel command line in cmdline.extra: unbalanced quoting`,
   337  	}} {
   338  		sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, append([][]string{
   339  			{"meta/snap.yaml", gadgetSnapYaml},
   340  		}, tc.files...))
   341  		var cmdline string
   342  		var err error
   343  		switch tc.which {
   344  		case "current":
   345  			cmdline, err = boot.ComposeRecoveryCommandLine(model, system, sf)
   346  		case "candidate":
   347  			cmdline, err = boot.ComposeCandidateRecoveryCommandLine(model, system, sf)
   348  		default:
   349  			c.Fatalf("unexpected command line type")
   350  		}
   351  		if tc.errMsg == "" {
   352  			c.Assert(err, IsNil)
   353  			c.Assert(cmdline, Equals, tc.expCommandLine)
   354  		} else {
   355  			c.Assert(err, ErrorMatches, tc.errMsg)
   356  		}
   357  	}
   358  }
   359  
   360  func (s *kernelCommandLineSuite) TestBootVarsForGadgetCommandLine(c *C) {
   361  	for _, tc := range []struct {
   362  		errMsg       string
   363  		files        [][]string
   364  		expectedVars map[string]string
   365  	}{{
   366  		files: [][]string{
   367  			{"cmdline.extra", "foo bar baz"},
   368  		},
   369  		expectedVars: map[string]string{
   370  			"snapd_extra_cmdline_args": "foo bar baz",
   371  			"snapd_full_cmdline_args":  "",
   372  		},
   373  	}, {
   374  		files: [][]string{
   375  			{"cmdline.extra", "snapd.debug=1"},
   376  		},
   377  		expectedVars: map[string]string{
   378  			"snapd_extra_cmdline_args": "snapd.debug=1",
   379  			"snapd_full_cmdline_args":  "",
   380  		},
   381  	}, {
   382  		files: [][]string{
   383  			{"cmdline.extra", "snapd_foo"},
   384  		},
   385  		errMsg: `cannot use kernel command line from gadget: invalid kernel command line in cmdline.extra: disallowed kernel argument \"snapd_foo\"`,
   386  	}, {
   387  		files: [][]string{
   388  			{"cmdline.full", "full foo bar baz"},
   389  		},
   390  		expectedVars: map[string]string{
   391  			"snapd_extra_cmdline_args": "",
   392  			"snapd_full_cmdline_args":  "full foo bar baz",
   393  		},
   394  	}, {
   395  		// with no arguments boot variables should be cleared
   396  		files: [][]string{},
   397  		expectedVars: map[string]string{
   398  			"snapd_extra_cmdline_args": "",
   399  			"snapd_full_cmdline_args":  "",
   400  		},
   401  	}} {
   402  		sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, append([][]string{
   403  			{"meta/snap.yaml", gadgetSnapYaml},
   404  		}, tc.files...))
   405  		vars, err := boot.BootVarsForTrustedCommandLineFromGadget(sf)
   406  		if tc.errMsg == "" {
   407  			c.Assert(err, IsNil)
   408  			c.Assert(vars, DeepEquals, tc.expectedVars)
   409  		} else {
   410  			c.Assert(err, ErrorMatches, tc.errMsg)
   411  		}
   412  	}
   413  }