github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/boot/fit/fit_test.go (about)

     1  // Copyright 2020 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package fit
     6  
     7  import (
     8  	"bytes"
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"os"
    13  	"path/filepath"
    14  	"reflect"
    15  	"strings"
    16  	"testing"
    17  
    18  	"github.com/mvdan/u-root-coreutils/pkg/boot"
    19  	"github.com/mvdan/u-root-coreutils/pkg/vfile"
    20  
    21  	"github.com/ProtonMail/go-crypto/openpgp"
    22  	"github.com/ProtonMail/go-crypto/openpgp/packet"
    23  )
    24  
    25  const (
    26  	// Number of configs in the fitimage.itb
    27  	fbcCnt = 2
    28  	// Size in bytes the of content for each image in the fitimage.itb
    29  	// This arbitrary value is defined in testdata/README.md for data creation.
    30  	imageSize = 100
    31  	// Fill patterns for the image content
    32  	kernel0Fill  = "k0"
    33  	kernel1Fill  = "k1"
    34  	initram0Fill = "i0"
    35  )
    36  
    37  func TestLoadConfig(t *testing.T) {
    38  	i, err := New("testdata/fitimage.itb")
    39  	if err != nil {
    40  		t.Fatal(err)
    41  	}
    42  
    43  	kn, rn, err := i.LoadConfig()
    44  	if err != nil {
    45  		t.Fatal(err)
    46  	}
    47  
    48  	t.Logf("kernel name: %s", kn)
    49  	t.Logf("ramdisk name: %s", rn)
    50  	if kn != "kernel@0" {
    51  		t.Errorf("Expected kernel %s, got %s", "kernel@0", kn)
    52  	}
    53  	if rn != "ramdisk@0" {
    54  		t.Errorf("Expected ramdisk %s, got %s", "ramdisk@0", rn)
    55  	}
    56  }
    57  
    58  func TestLoadConfigMiss(t *testing.T) {
    59  	i, err := New("testdata/fitimage.itb")
    60  	if err != nil {
    61  		t.Fatal(err)
    62  	}
    63  
    64  	i.ConfigOverride = "MagicNonExistentConfig"
    65  	kn, rn, err := i.LoadConfig()
    66  
    67  	if kn != "" {
    68  		t.Errorf("Kernel %s returned on expected config miss", kn)
    69  	}
    70  
    71  	if rn != "" {
    72  		t.Errorf("Initramfs %s returned on expected config miss", rn)
    73  	}
    74  
    75  	if err == nil {
    76  		t.Fatal("Expected error message for miss on FIT config, got nil")
    77  	}
    78  }
    79  
    80  func TestLoad(t *testing.T) {
    81  	keyFiles := []string{"key0", "key1"}
    82  
    83  	var keys []*openpgp.Entity
    84  	for _, k := range keyFiles {
    85  		b, err := os.ReadFile(filepath.Join("testdata", k))
    86  		if err != nil {
    87  			t.Fatal(err)
    88  		}
    89  		key, err := openpgp.ReadEntity(packet.NewReader(bytes.NewBuffer(b)))
    90  		if err != nil {
    91  			t.Fatal(err)
    92  		}
    93  		keys = append(keys, key)
    94  	}
    95  
    96  	for _, tt := range []struct {
    97  		desc           string
    98  		kernel         string
    99  		initram        string
   100  		keyring        openpgp.KeyRing
   101  		want           error
   102  		wantKernelFill string
   103  		wantInitFill   string
   104  	}{
   105  		{
   106  			desc:           "Successful kernel0/init0 read with key0",
   107  			keyring:        openpgp.EntityList{keys[0]},
   108  			kernel:         "kernel@0",
   109  			initram:        "ramdisk@0",
   110  			want:           nil,
   111  			wantKernelFill: kernel0Fill,
   112  			wantInitFill:   initram0Fill,
   113  		},
   114  		{
   115  			desc:           "Successful unsigned kernel1/init",
   116  			keyring:        nil,
   117  			kernel:         "kernel@1",
   118  			initram:        "ramdisk@0",
   119  			want:           nil,
   120  			wantKernelFill: kernel1Fill,
   121  			wantInitFill:   initram0Fill,
   122  		},
   123  		{
   124  			desc:           "Successful unsigned kernel1",
   125  			keyring:        nil,
   126  			kernel:         "kernel@1",
   127  			initram:        "",
   128  			want:           nil,
   129  			wantKernelFill: kernel1Fill,
   130  			wantInitFill:   "",
   131  		},
   132  		{
   133  			desc:           "bad kernel0 good init0 read with key1",
   134  			keyring:        openpgp.EntityList{keys[1]},
   135  			kernel:         "kernel@0",
   136  			initram:        "ramdisk@0",
   137  			want:           vfile.ErrUnsigned{},
   138  			wantKernelFill: "",
   139  			wantInitFill:   "",
   140  		},
   141  		{
   142  			desc:           "missing kernel",
   143  			keyring:        nil,
   144  			kernel:         "",
   145  			initram:        "ramdisk@0",
   146  			want:           fmt.Errorf(""),
   147  			wantKernelFill: "",
   148  			wantInitFill:   "",
   149  		},
   150  	} {
   151  		t.Run(tt.desc, func(t *testing.T) {
   152  			i, err := New("testdata/fitimage.itb")
   153  			if err != nil {
   154  				t.Fatal(err)
   155  			}
   156  
   157  			i.Kernel, i.InitRAMFS, i.KeyRing = tt.kernel, tt.initram, tt.keyring
   158  
   159  			defer func(old func(i *boot.LinuxImage, verbose bool) error) { loadImage = old }(loadImage)
   160  
   161  			loadImage = func(i *boot.LinuxImage, verbose bool) error {
   162  				if i == nil {
   163  					t.Errorf("Load() of kernel:%s, init:%s, keys: %v - passed nil to loadImage", tt.kernel, tt.initram, tt.keyring)
   164  					return nil
   165  				}
   166  
   167  				if i.Kernel == nil && tt.wantKernelFill != "" {
   168  					t.Errorf("loadImage gave nil kernel: want pattern '%s'", tt.wantKernelFill)
   169  				}
   170  				if i.Kernel != nil {
   171  					compareImage(t, "kernel", tt.wantKernelFill, i.Kernel)
   172  				}
   173  
   174  				if i.Initrd == nil && tt.wantInitFill != "" {
   175  					t.Errorf("loadImage gave nil initram: want pattern '%s'", tt.wantInitFill)
   176  				}
   177  				if i.Initrd != nil {
   178  					compareImage(t, "initram", tt.wantInitFill, i.Initrd)
   179  				}
   180  				return nil
   181  			}
   182  
   183  			gotErr := i.Load(true)
   184  
   185  			// Shallow check to verify the correct error
   186  			if (tt.want == nil && gotErr != nil) || reflect.TypeOf(gotErr) != reflect.TypeOf(tt.want) {
   187  				t.Errorf("Load() of kernel:%s, init:%s, keys: %v - got %T, want %T", tt.kernel, tt.initram, tt.keyring, gotErr, tt.want)
   188  			}
   189  		})
   190  	}
   191  }
   192  
   193  func compareImage(t *testing.T, name string, wantFill string, image io.ReaderAt) {
   194  	// Make sure we can only read imageSize by expecting an EOF instead of
   195  	// the imageSize+1 byte.
   196  	gotImage := make([]byte, imageSize+1)
   197  	gotSize, err := image.ReadAt(gotImage, 0)
   198  	if !errors.Is(err, io.EOF) {
   199  		t.Errorf("failed reading %s passed to loadImage: ReadAt(%d)/expected %d: got %v, want %v", name, imageSize+1, imageSize, err, io.EOF)
   200  	}
   201  	if gotSize != imageSize {
   202  		t.Errorf("failed reading %s passed to loadImage: bytes got %v bytes, want %v of pattern %s.\nReturned image:\t%v", name, gotSize, imageSize, wantFill, gotImage)
   203  	} else {
   204  		fill := []byte(wantFill)
   205  		for i := 0; i < gotSize; i++ {
   206  			if gotImage[i] != fill[i%len(fill)] {
   207  				t.Errorf("loadImage gave %s: %v, want pattern: \"%v...\". Mismatch at index %d: want %d, got %d", name, gotImage, wantFill, i, gotImage[i], fill[i%len(fill)])
   208  			}
   209  		}
   210  	}
   211  }
   212  
   213  func TestReadSignedImage(t *testing.T) {
   214  	keyFiles := []string{"key0", "key1"}
   215  
   216  	var keys []*openpgp.Entity
   217  	for _, k := range keyFiles {
   218  		b, err := os.ReadFile(filepath.Join("testdata", k))
   219  		if err != nil {
   220  			t.Fatal(err)
   221  		}
   222  		key, err := openpgp.ReadEntity(packet.NewReader(bytes.NewBuffer(b)))
   223  		if err != nil {
   224  			t.Fatal(err)
   225  		}
   226  		keys = append(keys, key)
   227  	}
   228  
   229  	for _, tt := range []struct {
   230  		desc             string
   231  		keyring          openpgp.KeyRing
   232  		image            string
   233  		want             error
   234  		isSignatureValid bool
   235  		wantContentFill  string
   236  	}{
   237  		{
   238  			desc:             "Successful kernel read with key0",
   239  			keyring:          openpgp.EntityList{keys[0]},
   240  			image:            "kernel@0",
   241  			want:             nil,
   242  			wantContentFill:  kernel0Fill,
   243  			isSignatureValid: true,
   244  		},
   245  		{
   246  			desc:             "Successful initram read with key0",
   247  			keyring:          openpgp.EntityList{keys[0]},
   248  			image:            "ramdisk@0",
   249  			want:             nil,
   250  			wantContentFill:  initram0Fill,
   251  			isSignatureValid: true,
   252  		},
   253  		{
   254  			desc:             "Successful initram read with key1",
   255  			keyring:          openpgp.EntityList{keys[1]},
   256  			image:            "ramdisk@0",
   257  			want:             nil,
   258  			wantContentFill:  initram0Fill,
   259  			isSignatureValid: true,
   260  		},
   261  		{
   262  			desc:             "Read unsigned kernel1",
   263  			keyring:          openpgp.EntityList{keys[0], keys[1]},
   264  			image:            "kernel@1",
   265  			want:             vfile.ErrUnsigned{},
   266  			wantContentFill:  kernel1Fill,
   267  			isSignatureValid: false,
   268  		},
   269  		{
   270  			desc:             "Read signed kernel0 with wrong key",
   271  			keyring:          openpgp.EntityList{keys[1]},
   272  			image:            "kernel@0",
   273  			want:             vfile.ErrUnsigned{},
   274  			wantContentFill:  kernel0Fill,
   275  			isSignatureValid: false,
   276  		},
   277  	} {
   278  		t.Run(tt.desc, func(t *testing.T) {
   279  			i, err := New("testdata/fitimage.itb")
   280  			if err != nil {
   281  				t.Fatal(err)
   282  			}
   283  
   284  			b, gotErr := i.ReadSignedImage(tt.image, tt.keyring)
   285  
   286  			// Shallow check to verify we're claiming it's signed or unsigned
   287  			if (tt.want == nil && gotErr != nil) || reflect.TypeOf(gotErr) != reflect.TypeOf(tt.want) {
   288  				t.Errorf("ReadSignedImage(%s, %v) = %T, want %T", tt.image, tt.keyring, gotErr, tt.want)
   289  			}
   290  
   291  			if isSignatureValid := (gotErr == nil); isSignatureValid != tt.isSignatureValid {
   292  				t.Errorf("isSignatureValid(%v) = %v, want %v", gotErr, isSignatureValid, tt.isSignatureValid)
   293  			}
   294  
   295  			if b != nil {
   296  				compareImage(t, tt.image, tt.wantContentFill, b)
   297  			}
   298  		})
   299  	}
   300  }
   301  
   302  func TestParseConfig(t *testing.T) {
   303  	f, err := os.Open("testdata/fitimage.itb")
   304  	if err != nil {
   305  		t.Fatal(err)
   306  	}
   307  
   308  	imgs, err := ParseConfig(f)
   309  	if err != nil {
   310  		t.Fatal(err)
   311  	}
   312  
   313  	if len(imgs) != fbcCnt {
   314  		t.Fatalf("Expected 2 images from ParseConfig, got %x", len(imgs))
   315  	}
   316  
   317  	cs := [fbcCnt]string{"conf@1", "conf_bz@1"}
   318  	for c, i := range imgs {
   319  		t.Logf("config name: %s", i.ConfigOverride)
   320  		t.Logf("kernel name: %s", i.Kernel)
   321  		t.Logf("ramdisk name: %s", i.InitRAMFS)
   322  		if i.ConfigOverride != cs[c] {
   323  			t.Errorf("Expected config %s, got %s", cs[c], i.ConfigOverride)
   324  		}
   325  		if i.Kernel != "kernel@0" {
   326  			t.Errorf("Expected kernel %s, got %s", "kernel@0", i.Kernel)
   327  		}
   328  		if i.InitRAMFS != "ramdisk@0" {
   329  			t.Errorf("Expected ramdisk %s, got %s", "ramdisk@0", i.InitRAMFS)
   330  		}
   331  	}
   332  }
   333  
   334  func TestParseConfigMiss(t *testing.T) {
   335  	f, err := os.Open("testdata/fitimage.its")
   336  	if err != nil {
   337  		t.Fatal(err)
   338  	}
   339  
   340  	imgs, err := ParseConfig(f)
   341  
   342  	if imgs != nil {
   343  		t.Errorf("Expected nil on bad image parse, got %#v", imgs)
   344  	}
   345  
   346  	if err == nil {
   347  		t.Fatal("Expected error on failed ParseConfig, got nil")
   348  	}
   349  }
   350  
   351  func TestLabel(t *testing.T) {
   352  	n, kn, rn := "conf_bz@1", "kernel@0", "ramdisk@0"
   353  	img := &Image{name: n, Kernel: kn, InitRAMFS: rn}
   354  	l := img.Label()
   355  	if !strings.Contains(l, n) {
   356  		t.Fatalf("Expected Image label to contain name %s, got %s", n, l)
   357  	}
   358  }
   359  
   360  func TestRank(t *testing.T) {
   361  	testRank := 2
   362  	img := &Image{BootRank: testRank}
   363  	l := img.Rank()
   364  	if l != testRank {
   365  		t.Fatalf("Expected Image rank %d, got %d", testRank, l)
   366  	}
   367  }