1// Copyright (c) 2017, Google Inc. 2// 3// Permission to use, copy, modify, and/or distribute this software for any 4// purpose with or without fee is hereby granted, provided that the above 5// copyright notice and this permission notice appear in all copies. 6// 7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 15// inject_hash parses an archive containing a file object file. It finds a FIPS 16// module inside that object, calculates its hash and replaces the default hash 17// value in the object with the calculated value. 18package main 19 20import ( 21 "bytes" 22 "crypto/hmac" 23 "crypto/sha256" 24 "debug/elf" 25 "encoding/binary" 26 "errors" 27 "flag" 28 "fmt" 29 "io" 30 "os" 31 "strings" 32 33 "boringssl.googlesource.com/boringssl/util/ar" 34 "boringssl.googlesource.com/boringssl/util/fipstools/fipscommon" 35) 36 37func do(outPath, oInput string, arInput string) error { 38 var objectBytes []byte 39 var isStatic bool 40 var perm os.FileMode 41 42 if len(arInput) > 0 { 43 isStatic = true 44 45 if len(oInput) > 0 { 46 return fmt.Errorf("-in-archive and -in-object are mutually exclusive") 47 } 48 49 fi, err := os.Stat(arInput) 50 if err != nil { 51 return err 52 } 53 perm = fi.Mode() 54 55 arFile, err := os.Open(arInput) 56 if err != nil { 57 return err 58 } 59 defer arFile.Close() 60 61 ar, err := ar.ParseAR(arFile) 62 if err != nil { 63 return err 64 } 65 66 if len(ar) != 1 { 67 return fmt.Errorf("expected one file in archive, but found %d", len(ar)) 68 } 69 70 for _, contents := range ar { 71 objectBytes = contents 72 } 73 } else if len(oInput) > 0 { 74 fi, err := os.Stat(oInput) 75 if err != nil { 76 return err 77 } 78 perm = fi.Mode() 79 80 if objectBytes, err = os.ReadFile(oInput); err != nil { 81 return err 82 } 83 isStatic = strings.HasSuffix(oInput, ".o") 84 } else { 85 return fmt.Errorf("exactly one of -in-archive or -in-object is required") 86 } 87 88 object, err := elf.NewFile(bytes.NewReader(objectBytes)) 89 if err != nil { 90 return errors.New("failed to parse object: " + err.Error()) 91 } 92 93 // Find the .text and, optionally, .data sections. 94 95 var textSection, rodataSection *elf.Section 96 var textSectionIndex, rodataSectionIndex elf.SectionIndex 97 for i, section := range object.Sections { 98 switch section.Name { 99 case ".text": 100 textSectionIndex = elf.SectionIndex(i) 101 textSection = section 102 case ".rodata": 103 rodataSectionIndex = elf.SectionIndex(i) 104 rodataSection = section 105 } 106 } 107 108 if textSection == nil { 109 return errors.New("failed to find .text section in object") 110 } 111 112 // Find the starting and ending symbols for the module. 113 114 var textStart, textEnd, rodataStart, rodataEnd *uint64 115 116 symbols, err := object.Symbols() 117 if err != nil { 118 return errors.New("failed to parse symbols: " + err.Error()) 119 } 120 121 for _, symbol := range symbols { 122 var base uint64 123 switch symbol.Section { 124 case textSectionIndex: 125 base = textSection.Addr 126 case rodataSectionIndex: 127 if rodataSection == nil { 128 continue 129 } 130 base = rodataSection.Addr 131 default: 132 continue 133 } 134 135 if isStatic { 136 // Static objects appear to have different semantics about whether symbol 137 // values are relative to their section or not. 138 base = 0 139 } else if symbol.Value < base { 140 return fmt.Errorf("symbol %q at %x, which is below base of %x", symbol.Name, symbol.Value, base) 141 } 142 143 value := symbol.Value - base 144 switch symbol.Name { 145 case "BORINGSSL_bcm_text_start": 146 if textStart != nil { 147 return errors.New("duplicate start symbol found") 148 } 149 textStart = &value 150 case "BORINGSSL_bcm_text_end": 151 if textEnd != nil { 152 return errors.New("duplicate end symbol found") 153 } 154 textEnd = &value 155 case "BORINGSSL_bcm_rodata_start": 156 if rodataStart != nil { 157 return errors.New("duplicate rodata start symbol found") 158 } 159 rodataStart = &value 160 case "BORINGSSL_bcm_rodata_end": 161 if rodataEnd != nil { 162 return errors.New("duplicate rodata end symbol found") 163 } 164 rodataEnd = &value 165 default: 166 continue 167 } 168 } 169 170 if textStart == nil || textEnd == nil { 171 return errors.New("could not find .text module boundaries in object") 172 } 173 174 if (rodataStart == nil) != (rodataSection == nil) { 175 return errors.New("rodata start marker inconsistent with rodata section presence") 176 } 177 178 if (rodataStart != nil) != (rodataEnd != nil) { 179 return errors.New("rodata marker presence inconsistent") 180 } 181 182 if max := textSection.Size; *textStart > max || *textStart > *textEnd || *textEnd > max { 183 return fmt.Errorf("invalid module .text boundaries: start: %x, end: %x, max: %x", *textStart, *textEnd, max) 184 } 185 186 if rodataSection != nil { 187 if max := rodataSection.Size; *rodataStart > max || *rodataStart > *rodataEnd || *rodataEnd > max { 188 return fmt.Errorf("invalid module .rodata boundaries: start: %x, end: %x, max: %x", *rodataStart, *rodataEnd, max) 189 } 190 } 191 192 // Extract the module from the .text section and hash it. 193 194 text := textSection.Open() 195 if _, err := text.Seek(int64(*textStart), 0); err != nil { 196 return errors.New("failed to seek to module start in .text: " + err.Error()) 197 } 198 moduleText := make([]byte, *textEnd-*textStart) 199 if _, err := io.ReadFull(text, moduleText); err != nil { 200 return errors.New("failed to read .text: " + err.Error()) 201 } 202 203 // Maybe extract the module's read-only data too 204 var moduleROData []byte 205 if rodataSection != nil { 206 rodata := rodataSection.Open() 207 if _, err := rodata.Seek(int64(*rodataStart), 0); err != nil { 208 return errors.New("failed to seek to module start in .rodata: " + err.Error()) 209 } 210 moduleROData = make([]byte, *rodataEnd-*rodataStart) 211 if _, err := io.ReadFull(rodata, moduleROData); err != nil { 212 return errors.New("failed to read .rodata: " + err.Error()) 213 } 214 } 215 216 var zeroKey [64]byte 217 mac := hmac.New(sha256.New, zeroKey[:]) 218 219 if moduleROData != nil { 220 var lengthBytes [8]byte 221 binary.LittleEndian.PutUint64(lengthBytes[:], uint64(len(moduleText))) 222 mac.Write(lengthBytes[:]) 223 mac.Write(moduleText) 224 225 binary.LittleEndian.PutUint64(lengthBytes[:], uint64(len(moduleROData))) 226 mac.Write(lengthBytes[:]) 227 mac.Write(moduleROData) 228 } else { 229 mac.Write(moduleText) 230 } 231 calculated := mac.Sum(nil) 232 233 // Replace the default hash value in the object with the calculated 234 // value and write it out. 235 236 offset := bytes.Index(objectBytes, fipscommon.UninitHashValue[:]) 237 if offset < 0 { 238 return errors.New("did not find uninitialised hash value in object file") 239 } 240 241 if bytes.Index(objectBytes[offset+1:], fipscommon.UninitHashValue[:]) >= 0 { 242 return errors.New("found two occurrences of uninitialised hash value in object file") 243 } 244 245 copy(objectBytes[offset:], calculated) 246 247 return os.WriteFile(outPath, objectBytes, perm&0777) 248} 249 250func main() { 251 arInput := flag.String("in-archive", "", "Path to a .a file") 252 oInput := flag.String("in-object", "", "Path to a .o file") 253 outPath := flag.String("o", "", "Path to output object") 254 255 flag.Parse() 256 257 if err := do(*outPath, *oInput, *arInput); err != nil { 258 fmt.Fprintf(os.Stderr, "%s\n", err) 259 os.Exit(1) 260 } 261} 262