github.com/linuxboot/fiano@v1.2.0/pkg/visitors/dxecleaner.go (about) 1 // Copyright 2018 the LinuxBoot 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 visitors 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "io" 12 "os" 13 "os/exec" 14 "os/signal" 15 "path/filepath" 16 "regexp" 17 "strings" 18 "syscall" 19 20 "github.com/linuxboot/fiano/pkg/guid" 21 "github.com/linuxboot/fiano/pkg/uefi" 22 ) 23 24 // DXECleaner removes DXEs sequentially in multiple rounds. Each round, an 25 // attempt is made to remove each DXE. The Test function determines if the 26 // removal was successful. Additional rounds are performed until all DXEs are 27 // removed. 28 type DXECleaner struct { 29 // This function tests whether the firmware boots. The return values can be: 30 // 31 // - (false, nil): The firmware was tested and failed to boot. 32 // - (false, err): The firmware was tested and failed to boot due to err. 33 // - (true, nil): The firmware was tested and booted. 34 // - (true, err): Failed to test the firmware due to err. 35 Test func(f uefi.Firmware) (bool, error) 36 37 // Predicate to determine whether a DXE can be removed. 38 Predicate FindPredicate 39 40 // List of GUIDs which were removed. 41 Removals []guid.GUID 42 43 // Logs are written to this writer. 44 W io.Writer 45 } 46 47 // Run wraps Visit and performs some setup and teardown tasks. 48 func (v *DXECleaner) Run(f uefi.Firmware) error { 49 var printf = func(format string, a ...interface{}) { 50 if v.W != nil { 51 fmt.Fprintf(v.W, format, a...) 52 } 53 } 54 55 // Find list of DXEs. 56 find := (&Find{Predicate: v.Predicate}) 57 if err := find.Run(f); err != nil { 58 return err 59 } 60 var dxes []guid.GUID 61 for i := range find.Matches { 62 dxes = append(dxes, find.Matches[i].(*uefi.File).Header.GUID) 63 } 64 if len(dxes) == 0 { 65 return errors.New("found no DXEs in firmware image") 66 } 67 68 // Print list of removals in a format which can be passed back into UTK. 69 defer func() { 70 printf("Summary of removed DXEs:\n") 71 if len(v.Removals) == 0 { 72 printf(" Could not remove any DXEs\n") 73 } else { 74 for _, r := range v.Removals { 75 printf(" remove %s \\\n", r) 76 } 77 } 78 }() 79 80 // Main algorithm to remove DXEs. 81 moreRoundsNeeded := true 82 for i := 0; moreRoundsNeeded; i++ { 83 printf("Beginning of round %d\n", i+1) 84 moreRoundsNeeded = false 85 for i := 0; i < len(dxes); i++ { 86 // Remove the DXE from the image. 87 printf("Trying to remove %v\n", dxes[i]) 88 remove := &Remove{Predicate: FindFileGUIDPredicate(dxes[i])} 89 if err := remove.Run(f); err != nil { 90 return err 91 } 92 93 if removedSuccessfully, err := v.Test(f); err == context.Canceled { 94 printf("Canceled by user %v!\n", dxes[i]) 95 return nil 96 } else if removedSuccessfully && err != nil { 97 return err 98 } else if removedSuccessfully { 99 printf(" Success %v!\n", dxes[i]) 100 v.Removals = append(v.Removals, dxes[i]) 101 dxes = append(dxes[:i], dxes[i+1:]...) 102 i-- 103 moreRoundsNeeded = true 104 } else { 105 printf(" Failed %v!\n", dxes[i]) 106 remove.Undo() 107 } 108 } 109 } 110 return nil 111 } 112 113 // Visit applies the DXEClearn visitor to any Firmware type. 114 func (v *DXECleaner) Visit(f uefi.Firmware) error { 115 return nil 116 } 117 118 // readBlackList returns a regex to filter DXEs according to the black list 119 // file. Each line in the black list is the GUID or name of a firmware file. 120 // Empty lines and lines beginning with '#' are ignored. 121 func parseBlackList(fileName, fileContents string) (string, error) { 122 blackList := "" 123 for i, line := range strings.Split(fileContents, "\n") { 124 line = strings.TrimSpace(line) 125 if line == "" || strings.HasPrefix(line, "#") { 126 continue 127 } 128 // skip multiple words, We need this for linuxboot file parsing 129 fw := strings.Split(line, " ")[0] 130 _, err := regexp.Compile(fw) 131 if err != nil { 132 return "", fmt.Errorf("cannot compile regex %q from blacklist file %q on line %v: %v", 133 fw, fileName, i, err) 134 } 135 blackList += "|(" + fw + ")" 136 } 137 if blackList != "" { 138 blackList = blackList[1:] 139 } 140 return blackList, nil 141 } 142 143 func init() { 144 register := func(args []string) (uefi.Visitor, error) { 145 // When the user enters CTRL-C, the DXECleaner should stop, but 146 // also output the current progress. 147 ctx, cancel := context.WithCancel(context.Background()) 148 c := make(chan os.Signal, 1) 149 signal.Notify(c, os.Interrupt) 150 go func() { 151 <-c 152 cancel() 153 }() 154 155 predicate := FindFileTypePredicate(uefi.FVFileTypeDriver) 156 157 // Create blacklist for DXEs which can be skipped. 158 useBlackList := len(args) == 2 159 if useBlackList { 160 fileName := args[1] 161 fileContents, err := os.ReadFile(fileName) 162 if err != nil { 163 return nil, fmt.Errorf("cannot read blacklist file %q: %v", fileName, err) 164 } 165 blackListRegex, err := parseBlackList(fileName, string(fileContents)) 166 if err != nil { 167 return nil, err 168 } 169 if blackListRegex != "" { 170 blackListPredicate, err := FindFilePredicate(blackListRegex) 171 if err != nil { 172 return nil, err 173 } 174 predicate = FindAndPredicate(predicate, FindNotPredicate(blackListPredicate)) 175 } 176 } 177 178 return &DXECleaner{ 179 Test: func(f uefi.Firmware) (bool, error) { 180 tmpDir, err := os.MkdirTemp("", "dxecleaner") 181 if err != nil { 182 return true, err 183 } 184 defer os.RemoveAll(tmpDir) 185 tmpFile := filepath.Join(tmpDir, "bios.bin") 186 187 if err := (&Save{tmpFile}).Run(f); err != nil { 188 return true, err 189 } 190 cmd := exec.CommandContext(ctx, args[0], tmpFile) 191 cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr 192 if err := cmd.Run(); err != nil { 193 if _, ok := err.(*exec.ExitError); !ok { 194 return true, err 195 } 196 status, ok := err.(*exec.ExitError).Sys().(syscall.WaitStatus) 197 if !ok { 198 return true, err 199 } 200 switch status.ExitStatus() { 201 case 1: 202 return true, err 203 case 2: 204 return false, err 205 default: 206 return true, fmt.Errorf("unexpected exit status %d", status.ExitStatus()) 207 } 208 } 209 return true, nil 210 }, 211 Predicate: predicate, 212 W: os.Stdout, 213 }, nil 214 } 215 216 RegisterCLI("dxecleaner", "automates removal of UEFI drivers", 1, register) 217 RegisterCLI("dxecleaner_blacklist", "automates removal of UEFI drivers with a blacklist file", 2, register) 218 }