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 "crypto/sha512" 25 "debug/elf" 26 "encoding/binary" 27 "errors" 28 "flag" 29 "fmt" 30 "io" 31 "io/ioutil" 32 "os" 33 "strings" 34 35 "boringssl.googlesource.com/boringssl/util/ar" 36 "boringssl.googlesource.com/boringssl/util/fipstools/fipscommon" 37) 38 39func do(outPath, oInput string, arInput string, useSHA256 bool) error { 40 var objectBytes []byte 41 var isStatic bool 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 arFile, err := os.Open(arInput) 50 if err != nil { 51 return err 52 } 53 defer arFile.Close() 54 55 ar, err := ar.ParseAR(arFile) 56 if err != nil { 57 return err 58 } 59 60 if len(ar) != 1 { 61 return fmt.Errorf("expected one file in archive, but found %d", len(ar)) 62 } 63 64 for _, contents := range ar { 65 objectBytes = contents 66 } 67 } else if len(oInput) > 0 { 68 var err error 69 if objectBytes, err = ioutil.ReadFile(oInput); err != nil { 70 return err 71 } 72 isStatic = strings.HasSuffix(oInput, ".o") 73 } else { 74 return fmt.Errorf("exactly one of -in-archive or -in-object is required") 75 } 76 77 object, err := elf.NewFile(bytes.NewReader(objectBytes)) 78 if err != nil { 79 return errors.New("failed to parse object: " + err.Error()) 80 } 81 82 // Find the .text and, optionally, .data sections. 83 84 var textSection, rodataSection *elf.Section 85 var textSectionIndex, rodataSectionIndex elf.SectionIndex 86 for i, section := range object.Sections { 87 switch section.Name { 88 case ".text": 89 textSectionIndex = elf.SectionIndex(i) 90 textSection = section 91 case ".rodata": 92 rodataSectionIndex = elf.SectionIndex(i) 93 rodataSection = section 94 } 95 } 96 97 if textSection == nil { 98 return errors.New("failed to find .text section in object") 99 } 100 101 // Find the starting and ending symbols for the module. 102 103 var textStart, textEnd, rodataStart, rodataEnd *uint64 104 105 symbols, err := object.Symbols() 106 if err != nil { 107 return errors.New("failed to parse symbols: " + err.Error()) 108 } 109 110 for _, symbol := range symbols { 111 var base uint64 112 switch symbol.Section { 113 case textSectionIndex: 114 base = textSection.Addr 115 case rodataSectionIndex: 116 if rodataSection == nil { 117 continue 118 } 119 base = rodataSection.Addr 120 default: 121 continue 122 } 123 124 if isStatic { 125 // Static objects appear to have different semantics about whether symbol 126 // values are relative to their section or not. 127 base = 0 128 } else if symbol.Value < base { 129 return fmt.Errorf("symbol %q at %x, which is below base of %x", symbol.Name, symbol.Value, base) 130 } 131 132 value := symbol.Value - base 133 switch symbol.Name { 134 case "BORINGSSL_bcm_text_start": 135 if textStart != nil { 136 return errors.New("duplicate start symbol found") 137 } 138 textStart = &value 139 case "BORINGSSL_bcm_text_end": 140 if textEnd != nil { 141 return errors.New("duplicate end symbol found") 142 } 143 textEnd = &value 144 case "BORINGSSL_bcm_rodata_start": 145 if rodataStart != nil { 146 return errors.New("duplicate rodata start symbol found") 147 } 148 rodataStart = &value 149 case "BORINGSSL_bcm_rodata_end": 150 if rodataEnd != nil { 151 return errors.New("duplicate rodata end symbol found") 152 } 153 rodataEnd = &value 154 default: 155 continue 156 } 157 } 158 159 if textStart == nil || textEnd == nil { 160 return errors.New("could not find .text module boundaries in object") 161 } 162 163 if (rodataStart == nil) != (rodataSection == nil) { 164 return errors.New("rodata start marker inconsistent with rodata section presence") 165 } 166 167 if (rodataStart != nil) != (rodataEnd != nil) { 168 return errors.New("rodata marker presence inconsistent") 169 } 170 171 if max := textSection.Size; *textStart > max || *textStart > *textEnd || *textEnd > max { 172 return fmt.Errorf("invalid module .text boundaries: start: %x, end: %x, max: %x", *textStart, *textEnd, max) 173 } 174 175 if rodataSection != nil { 176 if max := rodataSection.Size; *rodataStart > max || *rodataStart > *rodataEnd || *rodataEnd > max { 177 return fmt.Errorf("invalid module .rodata boundaries: start: %x, end: %x, max: %x", *rodataStart, *rodataEnd, max) 178 } 179 } 180 181 // Extract the module from the .text section and hash it. 182 183 text := textSection.Open() 184 if _, err := text.Seek(int64(*textStart), 0); err != nil { 185 return errors.New("failed to seek to module start in .text: " + err.Error()) 186 } 187 moduleText := make([]byte, *textEnd-*textStart) 188 if _, err := io.ReadFull(text, moduleText); err != nil { 189 return errors.New("failed to read .text: " + err.Error()) 190 } 191 192 // Maybe extract the module's read-only data too 193 var moduleROData []byte 194 if rodataSection != nil { 195 rodata := rodataSection.Open() 196 if _, err := rodata.Seek(int64(*rodataStart), 0); err != nil { 197 return errors.New("failed to seek to module start in .rodata: " + err.Error()) 198 } 199 moduleROData = make([]byte, *rodataEnd-*rodataStart) 200 if _, err := io.ReadFull(rodata, moduleROData); err != nil { 201 return errors.New("failed to read .rodata: " + err.Error()) 202 } 203 } 204 205 var zeroKey [64]byte 206 hashFunc := sha512.New 207 if useSHA256 { 208 hashFunc = sha256.New 209 } 210 mac := hmac.New(hashFunc, zeroKey[:]) 211 212 if moduleROData != nil { 213 var lengthBytes [8]byte 214 binary.LittleEndian.PutUint64(lengthBytes[:], uint64(len(moduleText))) 215 mac.Write(lengthBytes[:]) 216 mac.Write(moduleText) 217 218 binary.LittleEndian.PutUint64(lengthBytes[:], uint64(len(moduleROData))) 219 mac.Write(lengthBytes[:]) 220 mac.Write(moduleROData) 221 } else { 222 mac.Write(moduleText) 223 } 224 calculated := mac.Sum(nil) 225 226 // Replace the default hash value in the object with the calculated 227 // value and write it out. 228 229 offset := bytes.Index(objectBytes, fipscommon.UninitHashValue[:]) 230 if offset < 0 { 231 return errors.New("did not find uninitialised hash value in object file") 232 } 233 234 if bytes.Index(objectBytes[offset+1:], fipscommon.UninitHashValue[:]) >= 0 { 235 return errors.New("found two occurrences of uninitialised hash value in object file") 236 } 237 238 copy(objectBytes[offset:], calculated) 239 240 return ioutil.WriteFile(outPath, objectBytes, 0644) 241} 242 243func main() { 244 arInput := flag.String("in-archive", "", "Path to a .a file") 245 oInput := flag.String("in-object", "", "Path to a .o file") 246 outPath := flag.String("o", "", "Path to output object") 247 sha256 := flag.Bool("sha256", false, "Whether to use SHA-256 over SHA-512. This must match what the compiled module expects.") 248 249 flag.Parse() 250 251 if err := do(*outPath, *oInput, *arInput, *sha256); err != nil { 252 fmt.Fprintf(os.Stderr, "%s\n", err) 253 os.Exit(1) 254 } 255} 256