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