Skip to content

Commit

Permalink
Merge pull request #268 from chrisccoulson/efi-use-grub-prefix-for-de…
Browse files Browse the repository at this point in the history
…tection

efi: Make use of the grub's prefix for detection.

Avoid relying on the signature in order to detect an Ubuntu production
grub binary for binaries without a SBAT section, as this breaks test cases
in snapd where grub is re-signed. Instead, obtain the prefix from the
"mods" section (which is set to "/EFI/ubuntu" in Ubuntu).
  • Loading branch information
chrisccoulson authored Dec 12, 2023
2 parents 22aa666 + 8e2219e commit 7619639
Show file tree
Hide file tree
Showing 30 changed files with 620 additions and 26 deletions.
54 changes: 51 additions & 3 deletions efi/efi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ type mockPeImageHandle struct {
func (*mockPeImageHandle) Close() error { return nil }
func (h *mockPeImageHandle) Source() Image { return h.mockImage }

func (h *mockPeImageHandle) Machine() uint16 { return 0 }

func (h *mockPeImageHandle) OpenSection(name string) *io.SectionReader {
data, exists := h.sections[name]
if !exists {
Expand Down Expand Up @@ -176,6 +178,10 @@ func (h *mockPeImageHandle) newShimImageHandle() *mockShimImageHandle {
return &mockShimImageHandle{mockPeImageHandle: h}
}

func (h *mockPeImageHandle) newGrubImageHandle() *mockGrubImageHandle {
return &mockGrubImageHandle{mockPeImageHandle: h}
}

type mockShimImageHandle struct {
*mockPeImageHandle
}
Expand Down Expand Up @@ -205,6 +211,17 @@ func (h *mockShimImageHandle) ReadSbatLevel() (ShimSbatLevel, error) {
return *h.shimSbatLevel, nil
}

type mockGrubImageHandle struct {
*mockPeImageHandle
}

func (h *mockGrubImageHandle) Prefix() (string, error) {
if h.grubPrefix == "" {
return "", errors.New("no prefix")
}
return h.grubPrefix, nil
}

type mockImage struct {
sections map[string][]byte
sbat []SbatComponent
Expand All @@ -216,6 +233,8 @@ type mockImage struct {
shimVendorDb efi.SignatureDatabase
shimVendorDbFormat ShimVendorCertFormat
shimSbatLevel *ShimSbatLevel

grubPrefix string
}

func newMockImage() *mockImage {
Expand Down Expand Up @@ -278,6 +297,11 @@ func (i *mockImage) withShimSbatLevel(sbatLevel ShimSbatLevel) *mockImage {
return i
}

func (i *mockImage) withGrubPrefix(prefix string) *mockImage {
i.grubPrefix = prefix
return i
}

func newMockUbuntuShimImage15a(c *C) *mockImage {
return newMockImage().
appendSignatures(efitest.ReadWinCertificateAuthenticodeDetached(c, shimUbuntuSig1)).
Expand Down Expand Up @@ -318,7 +342,8 @@ func newMockUbuntuShimImage15_7(c *C) *mockImage {
func newMockUbuntuGrubImage1(c *C) *mockImage {
return newMockImage().
appendSignatures(efitest.ReadWinCertificateAuthenticodeDetached(c, grubUbuntuSig1)).
addSection("mods", nil)
addSection("mods", nil).
withGrubPrefix("/EFI/ubuntu")
}

func newMockUbuntuGrubImage2(c *C) *mockImage {
Expand All @@ -328,7 +353,8 @@ func newMockUbuntuGrubImage2(c *C) *mockImage {
withSbat([]SbatComponent{
{Name: "grub"},
{Name: "grub.ubuntu"},
})
}).
withGrubPrefix("/EFI/ubuntu")
}

func newMockUbuntuGrubImage3(c *C) *mockImage {
Expand All @@ -338,7 +364,8 @@ func newMockUbuntuGrubImage3(c *C) *mockImage {
withSbat([]SbatComponent{
{Name: "grub"},
{Name: "grub.ubuntu"},
})
}).
withGrubPrefix("/EFI/ubuntu")
}

func newMockUbuntuKernelImage1(c *C) *mockImage {
Expand Down Expand Up @@ -433,6 +460,27 @@ func (m *mockShimImageHandleMixin) TearDownTest(c *C) {
}
}

type mockGrubImageHandleMixin struct {
restore func()
}

func (m *mockGrubImageHandleMixin) SetUpTest(c *C) {
orig := NewGrubImageHandle
m.restore = MockNewGrubImageHandle(func(image PeImageHandle) GrubImageHandle {
h, ok := image.(*mockPeImageHandle)
if !ok {
return orig(image)
}
return h.newGrubImageHandle()
})
}

func (m *mockGrubImageHandleMixin) TearDownTest(c *C) {
if m.restore != nil {
m.restore()
}
}

type mockImageLoadHandlerMap map[Image]ImageLoadHandler

func (h mockImageLoadHandlerMap) LookupHandler(image PeImageHandle) (ImageLoadHandler, error) {
Expand Down
11 changes: 11 additions & 0 deletions efi/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ var (
MustParseShimVersion = mustParseShimVersion
NewestSbatLevel = newestSbatLevel
NewFwLoadHandler = newFwLoadHandler
NewGrubImageHandle = newGrubImageHandle
NewImageLoadHandlerLazyMap = newImageLoadHandlerLazyMap
NewImageRule = newImageRule
NewImageRules = newImageRules
Expand Down Expand Up @@ -86,6 +87,8 @@ var (
// unexported members of some unexported types.
type FwContext = fwContext
type GrubFlags = grubFlags
type GrubHasPrefix = grubHasPrefix
type GrubImageHandle = grubImageHandle
type GrubLoadHandler = grubLoadHandler
type ImageLoadHandler = imageLoadHandler
type ImageLoadHandlerMap = imageLoadHandlerMap
Expand Down Expand Up @@ -163,6 +166,14 @@ func MockNewFwLoadHandler(fn func(*tcglog.Log) ImageLoadHandler) (restore func()
}
}

func MockNewGrubImageHandle(fn func(peImageHandle) grubImageHandle) (restore func()) {
orig := newGrubImageHandle
newGrubImageHandle = fn
return func() {
newGrubImageHandle = orig
}
}

func MockNewShimImageHandle(fn func(peImageHandle) shimImageHandle) (restore func()) {
orig := newShimImageHandle
newShimImageHandle = fn
Expand Down
170 changes: 170 additions & 0 deletions efi/grub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2023 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package efi

import (
"encoding/binary"
"errors"
"fmt"
"io"
"math"

pe "github.com/snapcore/secboot/internal/pe1.14"
)

// grubObjTypePrefix is the module type for a module containing the
// grub prefix.
const grubObjTypePrefix uint32 = 3

// grubModuleHeader is an individual module header
type grubModuleHeader struct {
Type uint32 // the module type
Size uint32 // the module size, including the header
}

// grubModuleMagic represent the bytes that the mods section starts with.
const grubModuleMagic uint32 = 0x676d696d

// grubModuleInfo32 is the header at the start of the mods section, for
// 32 bit builds.
type grubModuleInfo32 struct {
Magic uint32
Offset uint32 // the offset of the first module, from the start of this header
Size uint32 // the size of all modules, including this header
}

// grubModuleInfo64 is the header at the start of the mods section, for
// 64 bit builds.
type grubModuleInfo64 struct {
Magic uint32
Padding uint32
Offset uint64 // the offset of the first module, from the start of this header
Size uint64 // the size of all modules, including this header
}

// grubModule represents a grub module
type grubModule struct {
Type uint32
*io.SectionReader
}

// grubImageHandle corresponds to a grub image.
type grubImageHandle interface {
peImageHandle

// Prefix returns the path that grub uses to load its configuration
// from the ESP.
Prefix() (string, error)
}

type grubImageHandleImpl struct {
peImageHandle
}

// newGrubImageHandle returns a new grubImageHandle for the supplied peImageHandle.
var newGrubImageHandle = func(image peImageHandle) grubImageHandle {
return &grubImageHandleImpl{peImageHandle: image}
}

func (h *grubImageHandleImpl) mods() ([]grubModule, error) {
section := h.OpenSection("mods")
if section == nil {
return nil, errors.New("no mods section")
}

var r *io.SectionReader
switch h.Machine() {
case pe.IMAGE_FILE_MACHINE_AMD64, pe.IMAGE_FILE_MACHINE_ARM64, pe.IMAGE_FILE_MACHINE_RISCV64:
var info grubModuleInfo64
if err := binary.Read(section, binary.LittleEndian, &info); err != nil {
return nil, fmt.Errorf("cannot obtain modules info: %w", err)
}
if info.Magic != grubModuleMagic {
return nil, errors.New("invalid modules magic")
}
if info.Offset > math.MaxInt64 {
return nil, errors.New("invalid modules offset")
}
if info.Size > math.MaxInt64 || info.Size < info.Offset {
return nil, errors.New("invalid modules size")
}
r = io.NewSectionReader(section, int64(info.Offset), int64(info.Size)-int64(info.Offset))
case pe.IMAGE_FILE_MACHINE_ARM, pe.IMAGE_FILE_MACHINE_I386, pe.IMAGE_FILE_MACHINE_RISCV32:
var info grubModuleInfo32
if err := binary.Read(section, binary.LittleEndian, &info); err != nil {
return nil, fmt.Errorf("cannot obtain modules info: %w", err)
}
if info.Magic != grubModuleMagic {
return nil, errors.New("invalid module magic")
}
if info.Size < info.Offset {
return nil, errors.New("invalid modules size")
}
r = io.NewSectionReader(section, int64(info.Offset), int64(info.Size)-int64(info.Offset))
default:
return nil, fmt.Errorf("unrecognized machine: %d", h.Machine())
}

var mods []grubModule

for {
var hdr grubModuleHeader
if err := binary.Read(r, binary.LittleEndian, &hdr); err != nil {
if err == io.EOF {
break
}
return nil, fmt.Errorf("cannot obtain module header: %w", err)
}

offset, _ := r.Seek(0, io.SeekCurrent)
size := int64(hdr.Size) - int64(binary.Size(hdr))
mods = append(mods, grubModule{
Type: hdr.Type,
SectionReader: io.NewSectionReader(r, offset, size),
})

if _, err := r.Seek(size, io.SeekCurrent); err != nil {
return nil, fmt.Errorf("cannot seek to next module: %w", err)
}
}

return mods, nil
}

func (h *grubImageHandleImpl) Prefix() (string, error) {
mods, err := h.mods()
if err != nil {
return "", err
}

for _, mod := range mods {
if mod.Type != grubObjTypePrefix {
continue
}

prefix, err := io.ReadAll(newCstringReader(mod))
if err != nil {
return "", fmt.Errorf("cannot obtain prefix: %w", err)
}
return string(prefix), nil
}

return "", nil
}
78 changes: 78 additions & 0 deletions efi/grub_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2023 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package efi_test

import (
. "gopkg.in/check.v1"

. "github.com/snapcore/secboot/efi"
)

type grubSuite struct{}

var _ = Suite(&grubSuite{})

func (s *grubSuite) TestGrubImageHandlePrefix1(c *C) {
image, err := OpenPeImage(NewFileImage("testdata/amd64/mockgrub.efi"))
c.Assert(err, IsNil)
defer image.Close()

grubImage := NewGrubImageHandle(image)

prefix, err := grubImage.Prefix()
c.Check(err, IsNil)
c.Check(prefix, Equals, "/EFI/ubuntu")
}

func (s *grubSuite) TestGrubImageHandlePrefix2(c *C) {
image, err := OpenPeImage(NewFileImage("testdata/amd64/mockgrub_debian.efi"))
c.Assert(err, IsNil)
defer image.Close()

grubImage := NewGrubImageHandle(image)

prefix, err := grubImage.Prefix()
c.Check(err, IsNil)
c.Check(prefix, Equals, "/EFI/debian")
}

func (s *grubSuite) TestGrubImageHandlePrefix3(c *C) {
image, err := OpenPeImage(NewFileImage("testdata/386/mockgrub.efi"))
c.Assert(err, IsNil)
defer image.Close()

grubImage := NewGrubImageHandle(image)

prefix, err := grubImage.Prefix()
c.Check(err, IsNil)
c.Check(prefix, Equals, "/EFI/ubuntu")
}

func (s *grubSuite) TestGrubImageHandlePrefixNone(c *C) {
image, err := OpenPeImage(NewFileImage("testdata/amd64/mockgrub_no_prefix.efi"))
c.Assert(err, IsNil)
defer image.Close()

grubImage := NewGrubImageHandle(image)

prefix, err := grubImage.Prefix()
c.Check(err, IsNil)
c.Check(prefix, Equals, "")
}
Loading

0 comments on commit 7619639

Please sign in to comment.