github.com/coreos/mantle@v0.13.0/kola/tests/misc/verity.go (about)

     1  // Copyright 2016 CoreOS, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package misc
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"strings"
    21  
    22  	"github.com/coreos/mantle/kola/cluster"
    23  	"github.com/coreos/mantle/kola/register"
    24  	"github.com/coreos/mantle/kola/tests/util"
    25  	"github.com/coreos/mantle/platform"
    26  )
    27  
    28  func init() {
    29  	register.Register(&register.Test{
    30  		Run:         Verity,
    31  		ClusterSize: 1,
    32  		Name:        "cl.verity",
    33  		Distros:     []string{"cl"},
    34  	})
    35  }
    36  
    37  func Verity(c cluster.TestCluster) {
    38  	c.Run("verify", VerityVerify)
    39  	// modifies disk; must run last
    40  	c.Run("corruption", VerityCorruption)
    41  }
    42  
    43  // Verity verification tests.
    44  // TODO(mischief): seems like a good candidate for kolet.
    45  
    46  // VerityVerify asserts that the filesystem mounted on /usr matches the
    47  // dm-verity hash that is embedded in the CoreOS kernel.
    48  func VerityVerify(c cluster.TestCluster) {
    49  	m := c.Machines()[0]
    50  
    51  	// extract verity hash from kernel
    52  	hash := c.MustSSH(m, "dd if=/boot/coreos/vmlinuz-a skip=64 count=64 bs=1 status=none")
    53  
    54  	// find /usr dev
    55  	usrdev := util.GetUsrDeviceNode(c, m)
    56  
    57  	// figure out partition size for hash dev offset
    58  	offset := c.MustSSH(m, "sudo e2size "+usrdev)
    59  	offset = bytes.TrimSpace(offset)
    60  
    61  	c.MustSSH(m, fmt.Sprintf("sudo veritysetup verify --verbose --hash-offset=%s %s %s %s", offset, usrdev, usrdev, hash))
    62  }
    63  
    64  // VerityCorruption asserts that a machine will fail to read a file from a
    65  // verify filesystem whose blocks have been modified.
    66  func VerityCorruption(c cluster.TestCluster) {
    67  	m := c.Machines()[0]
    68  	// skip unless we are actually using verity
    69  	skipUnlessVerity(c, m)
    70  
    71  	// assert that dm shows verity is in use and the device is valid (V)
    72  	out := c.MustSSH(m, "sudo dmsetup --target verity status usr")
    73  
    74  	fields := strings.Fields(string(out))
    75  	if len(fields) != 4 {
    76  		c.Fatalf("failed checking dmsetup status of usr: not enough fields in output (got %d)", len(fields))
    77  	}
    78  
    79  	if fields[3] != "V" {
    80  		c.Fatalf("dmsetup status usr reports verity is not valid!")
    81  	}
    82  
    83  	// corrupt a file on disk and flush disk caches.
    84  	// try setting NAME=CoreOS to NAME=LulzOS in /usr/lib/os-release
    85  
    86  	// get usr device, probably vda3
    87  	usrdev := util.GetUsrDeviceNode(c, m)
    88  
    89  	// poke bytes into /usr/lib/os-release
    90  	c.MustSSH(m, fmt.Sprintf(`echo NAME=LulzOS | sudo dd of=%s seek=$(expr $(sudo debugfs -R "blocks /lib/os-release" %s 2>/dev/null) \* 4096) bs=1 status=none`, usrdev, usrdev))
    91  
    92  	// make sure we flush everything so cat has to go through to the device backing verity.
    93  	c.MustSSH(m, "sudo /bin/sh -c 'sync; echo -n 3 >/proc/sys/vm/drop_caches'")
    94  
    95  	// read the file back. if we can read it successfully, verity did not do its job.
    96  	out, stderr, err := m.SSH("cat /usr/lib/os-release")
    97  	if err == nil {
    98  		c.Fatalf("verity did not prevent reading a corrupted file!")
    99  	}
   100  	for _, line := range strings.Split(string(stderr), "\n") {
   101  		// cat is expected to fail on EIO; report other errors
   102  		if line != "cat: /usr/lib/os-release: Input/output error" {
   103  			c.Log(line)
   104  		}
   105  	}
   106  
   107  	// assert that dm shows verity device is now corrupted (C)
   108  	out = c.MustSSH(m, "sudo dmsetup --target verity status usr")
   109  
   110  	fields = strings.Fields(string(out))
   111  	if len(fields) != 4 {
   112  		c.Fatalf("failed checking dmsetup status of usr: not enough fields in output (got %d)", len(fields))
   113  	}
   114  
   115  	if fields[3] != "C" {
   116  		c.Fatalf("dmsetup status usr reports verity is valid after corruption!")
   117  	}
   118  }
   119  
   120  func skipUnlessVerity(c cluster.TestCluster, m platform.Machine) {
   121  	// figure out if we are actually using verity
   122  	out, err := c.SSH(m, "sudo veritysetup status usr")
   123  	if err != nil && bytes.Equal(out, []byte("/dev/mapper/usr is inactive.")) {
   124  		// verity not in use, so skip.
   125  		c.Skip("verity is not enabled")
   126  	} else if err != nil {
   127  		c.Fatalf("failed checking verity status: %s: %v", out, err)
   128  	}
   129  }