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