| Colin Cross | 36f55aa | 2022-03-21 18:46:41 -0700 | [diff] [blame] | 1 | // Copyright 2022 Google Inc. All rights reserved. | 
 | 2 | // | 
 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); | 
 | 4 | // you may not use this file except in compliance with the License. | 
 | 5 | // You may obtain a copy of the License at | 
 | 6 | // | 
 | 7 | //     http://www.apache.org/licenses/LICENSE-2.0 | 
 | 8 | // | 
 | 9 | // Unless required by applicable law or agreed to in writing, software | 
 | 10 | // distributed under the License is distributed on an "AS IS" BASIS, | 
 | 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | 12 | // See the License for the specific language governing permissions and | 
 | 13 | // limitations under the License. | 
 | 14 |  | 
| Peter Collingbourne | 941ff1d | 2024-03-14 21:17:21 -0700 | [diff] [blame] | 15 | package elf | 
| Colin Cross | 36f55aa | 2022-03-21 18:46:41 -0700 | [diff] [blame] | 16 |  | 
 | 17 | import ( | 
 | 18 | 	"debug/elf" | 
 | 19 | 	"encoding/binary" | 
 | 20 | 	"encoding/hex" | 
| Colin Cross | 6285c65 | 2022-04-05 18:06:15 -0700 | [diff] [blame] | 21 | 	"errors" | 
| Colin Cross | 36f55aa | 2022-03-21 18:46:41 -0700 | [diff] [blame] | 22 | 	"fmt" | 
 | 23 | 	"io" | 
| Colin Cross | 6285c65 | 2022-04-05 18:06:15 -0700 | [diff] [blame] | 24 | 	"os" | 
| Colin Cross | 36f55aa | 2022-03-21 18:46:41 -0700 | [diff] [blame] | 25 | ) | 
 | 26 |  | 
 | 27 | const gnuBuildID = "GNU\x00" | 
 | 28 |  | 
| Peter Collingbourne | 941ff1d | 2024-03-14 21:17:21 -0700 | [diff] [blame] | 29 | // Identifier extracts the elf build ID from an elf file.  If allowMissing is true it returns | 
| Colin Cross | 36f55aa | 2022-03-21 18:46:41 -0700 | [diff] [blame] | 30 | // an empty identifier if the file exists but the build ID note does not. | 
| Peter Collingbourne | 941ff1d | 2024-03-14 21:17:21 -0700 | [diff] [blame] | 31 | func Identifier(filename string, allowMissing bool) (string, error) { | 
| Colin Cross | 6285c65 | 2022-04-05 18:06:15 -0700 | [diff] [blame] | 32 | 	f, err := os.Open(filename) | 
| Colin Cross | 36f55aa | 2022-03-21 18:46:41 -0700 | [diff] [blame] | 33 | 	if err != nil { | 
 | 34 | 		return "", fmt.Errorf("failed to open %s: %w", filename, err) | 
 | 35 | 	} | 
 | 36 | 	defer f.Close() | 
 | 37 |  | 
| Colin Cross | 6285c65 | 2022-04-05 18:06:15 -0700 | [diff] [blame] | 38 | 	return elfIdentifierFromReaderAt(f, filename, allowMissing) | 
 | 39 | } | 
 | 40 |  | 
| Colin Cross | 338df53 | 2022-04-11 18:42:34 -0700 | [diff] [blame] | 41 | // elfIdentifierFromReaderAt extracts the elf build ID from a ReaderAt.  If allowMissing is true it | 
 | 42 | // returns an empty identifier if the file exists but the build ID note does not. | 
| Colin Cross | 6285c65 | 2022-04-05 18:06:15 -0700 | [diff] [blame] | 43 | func elfIdentifierFromReaderAt(r io.ReaderAt, filename string, allowMissing bool) (string, error) { | 
 | 44 | 	f, err := elf.NewFile(r) | 
 | 45 | 	if err != nil { | 
 | 46 | 		if allowMissing { | 
| Colin Cross | 338df53 | 2022-04-11 18:42:34 -0700 | [diff] [blame] | 47 | 			if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) { | 
| Colin Cross | 6285c65 | 2022-04-05 18:06:15 -0700 | [diff] [blame] | 48 | 				return "", nil | 
 | 49 | 			} | 
 | 50 | 			if _, ok := err.(*elf.FormatError); ok { | 
 | 51 | 				// The file was not an elf file. | 
 | 52 | 				return "", nil | 
 | 53 | 			} | 
 | 54 | 		} | 
 | 55 | 		return "", fmt.Errorf("failed to parse elf file %s: %w", filename, err) | 
 | 56 | 	} | 
 | 57 | 	defer f.Close() | 
 | 58 |  | 
| Colin Cross | 36f55aa | 2022-03-21 18:46:41 -0700 | [diff] [blame] | 59 | 	buildIDNote := f.Section(".note.gnu.build-id") | 
 | 60 | 	if buildIDNote == nil { | 
 | 61 | 		if allowMissing { | 
 | 62 | 			return "", nil | 
 | 63 | 		} | 
 | 64 | 		return "", fmt.Errorf("failed to find .note.gnu.build-id in  %s", filename) | 
 | 65 | 	} | 
 | 66 |  | 
 | 67 | 	buildIDs, err := readNote(buildIDNote.Open(), f.ByteOrder) | 
 | 68 | 	if err != nil { | 
 | 69 | 		return "", fmt.Errorf("failed to read .note.gnu.build-id: %w", err) | 
 | 70 | 	} | 
 | 71 |  | 
 | 72 | 	for name, desc := range buildIDs { | 
 | 73 | 		if name == gnuBuildID { | 
 | 74 | 			return hex.EncodeToString(desc), nil | 
 | 75 | 		} | 
 | 76 | 	} | 
 | 77 |  | 
 | 78 | 	return "", nil | 
 | 79 | } | 
 | 80 |  | 
 | 81 | // readNote reads the contents of a note section, returning it as a map from name to descriptor. | 
 | 82 | func readNote(note io.Reader, byteOrder binary.ByteOrder) (map[string][]byte, error) { | 
 | 83 | 	var noteHeader struct { | 
 | 84 | 		Namesz uint32 | 
 | 85 | 		Descsz uint32 | 
 | 86 | 		Type   uint32 | 
 | 87 | 	} | 
 | 88 |  | 
 | 89 | 	notes := make(map[string][]byte) | 
 | 90 | 	for { | 
 | 91 | 		err := binary.Read(note, byteOrder, ¬eHeader) | 
 | 92 | 		if err != nil { | 
 | 93 | 			if err == io.EOF { | 
 | 94 | 				return notes, nil | 
 | 95 | 			} | 
 | 96 | 			return nil, fmt.Errorf("failed to read note header: %w", err) | 
 | 97 | 		} | 
 | 98 |  | 
 | 99 | 		nameBuf := make([]byte, align4(noteHeader.Namesz)) | 
 | 100 | 		err = binary.Read(note, byteOrder, &nameBuf) | 
 | 101 | 		if err != nil { | 
 | 102 | 			return nil, fmt.Errorf("failed to read note name: %w", err) | 
 | 103 | 		} | 
 | 104 | 		name := string(nameBuf[:noteHeader.Namesz]) | 
 | 105 |  | 
 | 106 | 		descBuf := make([]byte, align4(noteHeader.Descsz)) | 
 | 107 | 		err = binary.Read(note, byteOrder, &descBuf) | 
 | 108 | 		if err != nil { | 
 | 109 | 			return nil, fmt.Errorf("failed to read note desc: %w", err) | 
 | 110 | 		} | 
 | 111 | 		notes[name] = descBuf[:noteHeader.Descsz] | 
 | 112 | 	} | 
 | 113 | } | 
 | 114 |  | 
 | 115 | // align4 rounds the input up to the next multiple of 4. | 
 | 116 | func align4(i uint32) uint32 { | 
 | 117 | 	return (i + 3) &^ 3 | 
 | 118 | } |