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 1 commit
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
51 changes: 48 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,14 @@ func (h *mockShimImageHandle) ReadSbatLevel() (ShimSbatLevel, error) {
return *h.shimSbatLevel, nil
}

type mockGrubImageHandle struct {
*mockPeImageHandle
}

func (h *mockGrubImageHandle) Prefix() (string, error) {
return h.grubPrefix, nil
}

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

grubPrefix string
}

func newMockImage() *mockImage {
Expand Down Expand Up @@ -278,6 +294,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 +339,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 +350,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 +361,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 +457,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
10 changes: 10 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,7 @@ var (
// unexported members of some unexported types.
type FwContext = fwContext
type GrubFlags = grubFlags
type GrubImageHandle = grubImageHandle
type GrubLoadHandler = grubLoadHandler
type ImageLoadHandler = imageLoadHandler
type ImageLoadHandlerMap = imageLoadHandlerMap
Expand Down Expand Up @@ -163,6 +165,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
158 changes: 158 additions & 0 deletions efi/grub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// -*- 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"
)

const grubObjTypePrefix uint32 = 3

type grubModuleHeader struct {
Type uint32
Size uint32
}

const grubModuleMagic uint32 = 0x676d696d

type grubModuleInfo32 struct {
Magic uint32
Offset uint32
Size uint32
}

type grubModuleInfo64 struct {
Magic uint32
Padding uint32
Offset uint64
Size uint64
}

type grubModule struct {
Type uint32
*io.SectionReader
}

type grubImageHandle interface {
peImageHandle

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.Size > math.MaxInt64 {
Copy link
Contributor

Choose a reason for hiding this comment

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

info.Offset?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This was addressed with 421f90a

return nil, errors.New("invalid modules offset")
}
if info.Size > math.MaxInt64 || info.Size < uint64(binary.Size(info)) {
return nil, errors.New("invalid modules size")
}
r = io.NewSectionReader(section, int64(info.Offset), int64(info.Size)-int64(binary.Size(info)))
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 < uint32(binary.Size(info.Size)) {
return nil, errors.New("invalid modules size")
}
r = io.NewSectionReader(section, int64(info.Offset), int64(info.Size)-int64(binary.Size(info)))
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
}
13 changes: 12 additions & 1 deletion efi/image_load_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ func (c *mockImageLoadHandlerConstructor) NewImageLoadHandler(image PeImageHandl

type imageLoadHandlerSuite struct {
mockShimImageHandleMixin
mockGrubImageHandleMixin
}

func (s *imageLoadHandlerSuite) SetUpTest(c *C) {
s.mockShimImageHandleMixin.SetUpTest(c)
s.mockGrubImageHandleMixin.SetUpTest(c)
}

func (s *imageLoadHandlerSuite) TearDownTest(c *C) {
s.mockShimImageHandleMixin.TearDownTest(c)
s.mockGrubImageHandleMixin.TearDownTest(c)
}

var _ = Suite(&imageLoadHandlerSuite{})
Expand Down Expand Up @@ -131,5 +142,5 @@ func (s *imageLoadHandlerSuite) TestDefaultLookupFallback(c *C) {
handler, err := m.LookupHandler(image.newPeImageHandle())
c.Assert(err, IsNil)
c.Assert(handler, testutil.ConvertibleTo, &GrubLoadHandler{})
c.Check(handler, DeepEquals, new(GrubLoadHandler))
c.Check(handler.(*GrubLoadHandler).Flags, Equals, GrubChainloaderUsesShimProtocol)
}
12 changes: 12 additions & 0 deletions efi/image_rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,15 @@ func (p *shimVersionPredicate) Matches(image peImageHandle) (bool, error) {
return false, fmt.Errorf("invalid shim version operator %s", p.operator)
}
}

type grubHasPrefix string

func (p grubHasPrefix) Matches(image peImageHandle) (bool, error) {
grub := newGrubImageHandle(image)
prefix, err := grub.Prefix()
if err != nil {
return false, fmt.Errorf("cannot obtain grub prefix: %w", err)
}

return prefix == string(p), nil
}
14 changes: 11 additions & 3 deletions efi/image_rules_defs.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ func makeMicrosoftUEFICASecureBootNamespaceRules() *secureBootNamespaceRules {
),
imageMatchesAll(
imageSectionExists("mods"),
imageSignedByOrganization("Canonical Ltd."),
grubHasPrefix("/EFI/ubuntu"),
Copy link
Collaborator

Choose a reason for hiding this comment

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

do we still need all the changes from the snakeoil PR?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We do

),
),
newGrubLoadHandlerConstructor(grubChainloaderUsesShimProtocol).New,
Expand Down Expand Up @@ -209,14 +209,22 @@ func makeFallbackImageRules() *imageRules {
imageSectionExists(".vendor_cert"),
newShimLoadHandler,
),
// Ubuntu grub
newImageRule(
"grub",
imageMatchesAll(
imageSectionExists("mods"),
grubHasPrefix("/EFI/ubuntu"),
),
newGrubLoadHandlerConstructor(grubChainloaderUsesShimProtocol).New,
),
// Grub
newImageRule(
"grub",
imageSectionExists("mods"),
newGrubLoadHandler,
),
// TODO: add rules for Ubuntu Core UKI and Ubuntu grub that are not part of
// the MS UEFI CA?
// TODO: add rules for Ubuntu Core UKIs that are not part of the MS UEFI CA
//
// Catch-all for unrecognized leaf images
newImageRule(
Expand Down
Loading