Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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))
Comment on lines +109 to +120
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this case is not tested

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
Loading