1// Copyright 2017 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package buildid 6 7import ( 8 "bytes" 9 "crypto/sha256" 10 "debug/elf" 11 "encoding/binary" 12 "internal/obscuretestdata" 13 "os" 14 "reflect" 15 "strings" 16 "testing" 17) 18 19const ( 20 expectedID = "abcdefghijklmnopqrstuvwxyz.1234567890123456789012345678901234567890123456789012345678901234" 21 newID = "bcdefghijklmnopqrstuvwxyza.2345678901234567890123456789012345678901234567890123456789012341" 22) 23 24func TestReadFile(t *testing.T) { 25 f, err := os.CreateTemp("", "buildid-test-") 26 if err != nil { 27 t.Fatal(err) 28 } 29 tmp := f.Name() 30 defer os.Remove(tmp) 31 f.Close() 32 33 // Use obscured files to prevent Apple’s notarization service from 34 // mistaking them as candidates for notarization and rejecting the entire 35 // toolchain. 36 // See golang.org/issue/34986 37 var files = []string{ 38 "p.a.base64", 39 "a.elf.base64", 40 "a.macho.base64", 41 "a.pe.base64", 42 } 43 44 for _, name := range files { 45 f, err := obscuretestdata.DecodeToTempFile("testdata/" + name) 46 if err != nil { 47 t.Errorf("obscuretestdata.DecodeToTempFile(testdata/%s): %v", name, err) 48 continue 49 } 50 defer os.Remove(f) 51 id, err := ReadFile(f) 52 if id != expectedID || err != nil { 53 t.Errorf("ReadFile(testdata/%s) = %q, %v, want %q, nil", f, id, err, expectedID) 54 } 55 old := readSize 56 readSize = 2048 57 id, err = ReadFile(f) 58 readSize = old 59 if id != expectedID || err != nil { 60 t.Errorf("ReadFile(%s) [readSize=2k] = %q, %v, want %q, nil", f, id, err, expectedID) 61 } 62 63 data, err := os.ReadFile(f) 64 if err != nil { 65 t.Fatal(err) 66 } 67 m, _, err := FindAndHash(bytes.NewReader(data), expectedID, 1024) 68 if err != nil { 69 t.Errorf("FindAndHash(%s): %v", f, err) 70 continue 71 } 72 if err := os.WriteFile(tmp, data, 0666); err != nil { 73 t.Error(err) 74 continue 75 } 76 tf, err := os.OpenFile(tmp, os.O_WRONLY, 0) 77 if err != nil { 78 t.Error(err) 79 continue 80 } 81 err = Rewrite(tf, m, newID) 82 err2 := tf.Close() 83 if err != nil { 84 t.Errorf("Rewrite(%s): %v", f, err) 85 continue 86 } 87 if err2 != nil { 88 t.Fatal(err2) 89 } 90 91 id, err = ReadFile(tmp) 92 if id != newID || err != nil { 93 t.Errorf("ReadFile(%s after Rewrite) = %q, %v, want %q, nil", f, id, err, newID) 94 } 95 96 // Test an ELF PT_NOTE segment with an Align field of 0. 97 // Do this by rewriting the file data. 98 if strings.Contains(name, "elf") { 99 // We only expect a 64-bit ELF file. 100 if elf.Class(data[elf.EI_CLASS]) != elf.ELFCLASS64 { 101 continue 102 } 103 104 // We only expect a little-endian ELF file. 105 if elf.Data(data[elf.EI_DATA]) != elf.ELFDATA2LSB { 106 continue 107 } 108 order := binary.LittleEndian 109 110 var hdr elf.Header64 111 if err := binary.Read(bytes.NewReader(data), order, &hdr); err != nil { 112 t.Error(err) 113 continue 114 } 115 116 phoff := hdr.Phoff 117 phnum := int(hdr.Phnum) 118 phsize := uint64(hdr.Phentsize) 119 120 for i := 0; i < phnum; i++ { 121 var phdr elf.Prog64 122 if err := binary.Read(bytes.NewReader(data[phoff:]), order, &phdr); err != nil { 123 t.Error(err) 124 continue 125 } 126 127 if elf.ProgType(phdr.Type) == elf.PT_NOTE { 128 // Increase the size so we keep 129 // reading notes. 130 order.PutUint64(data[phoff+4*8:], phdr.Filesz+1) 131 132 // Clobber the Align field to zero. 133 order.PutUint64(data[phoff+6*8:], 0) 134 135 // Clobber the note type so we 136 // keep reading notes. 137 order.PutUint32(data[phdr.Off+12:], 0) 138 } 139 140 phoff += phsize 141 } 142 143 if err := os.WriteFile(tmp, data, 0666); err != nil { 144 t.Error(err) 145 continue 146 } 147 148 id, err := ReadFile(tmp) 149 // Because we clobbered the note type above, 150 // we don't expect to see a Go build ID. 151 // The issue we are testing for was a crash 152 // in Readfile; see issue #62097. 153 if id != "" || err != nil { 154 t.Errorf("ReadFile with zero ELF Align = %q, %v, want %q, nil", id, err, "") 155 continue 156 } 157 } 158 } 159} 160 161func TestFindAndHash(t *testing.T) { 162 buf := make([]byte, 64) 163 buf2 := make([]byte, 64) 164 id := make([]byte, 8) 165 zero := make([]byte, 8) 166 for i := range id { 167 id[i] = byte(i) 168 } 169 numError := 0 170 errorf := func(msg string, args ...any) { 171 t.Errorf(msg, args...) 172 if numError++; numError > 20 { 173 t.Logf("stopping after too many errors") 174 t.FailNow() 175 } 176 } 177 for bufSize := len(id); bufSize <= len(buf); bufSize++ { 178 for j := range buf { 179 for k := 0; k < 2*len(id) && j+k < len(buf); k++ { 180 for i := range buf { 181 buf[i] = 1 182 } 183 copy(buf[j:], id) 184 copy(buf[j+k:], id) 185 var m []int64 186 if j+len(id) <= j+k { 187 m = append(m, int64(j)) 188 } 189 if j+k+len(id) <= len(buf) { 190 m = append(m, int64(j+k)) 191 } 192 copy(buf2, buf) 193 for _, p := range m { 194 copy(buf2[p:], zero) 195 } 196 h := sha256.Sum256(buf2) 197 198 matches, hash, err := FindAndHash(bytes.NewReader(buf), string(id), bufSize) 199 if err != nil { 200 errorf("bufSize=%d j=%d k=%d: findAndHash: %v", bufSize, j, k, err) 201 continue 202 } 203 if !reflect.DeepEqual(matches, m) { 204 errorf("bufSize=%d j=%d k=%d: findAndHash: matches=%v, want %v", bufSize, j, k, matches, m) 205 continue 206 } 207 if hash != h { 208 errorf("bufSize=%d j=%d k=%d: findAndHash: matches correct, but hash=%x, want %x", bufSize, j, k, hash, h) 209 } 210 } 211 } 212 } 213} 214 215func TestExcludedReader(t *testing.T) { 216 const s = "0123456789abcdefghijklmn" 217 tests := []struct { 218 start, end int64 // excluded range 219 results []string // expected results of reads 220 }{ 221 {12, 15, []string{"0123456789", "ab\x00\x00\x00fghij", "klmn"}}, // within one read 222 {8, 21, []string{"01234567\x00\x00", "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", "\x00lmn"}}, // across multiple reads 223 {10, 20, []string{"0123456789", "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", "klmn"}}, // a whole read 224 {0, 5, []string{"\x00\x00\x00\x00\x0056789", "abcdefghij", "klmn"}}, // start 225 {12, 24, []string{"0123456789", "ab\x00\x00\x00\x00\x00\x00\x00\x00", "\x00\x00\x00\x00"}}, // end 226 } 227 p := make([]byte, 10) 228 for _, test := range tests { 229 r := &excludedReader{strings.NewReader(s), 0, test.start, test.end} 230 for _, res := range test.results { 231 n, err := r.Read(p) 232 if err != nil { 233 t.Errorf("read failed: %v", err) 234 } 235 if n != len(res) { 236 t.Errorf("unexpected number of bytes read: want %d, got %d", len(res), n) 237 } 238 if string(p[:n]) != res { 239 t.Errorf("unexpected bytes: want %q, got %q", res, p[:n]) 240 } 241 } 242 } 243} 244 245func TestEmptyID(t *testing.T) { 246 r := strings.NewReader("aha!") 247 matches, hash, err := FindAndHash(r, "", 1000) 248 if matches != nil || hash != ([32]byte{}) || err == nil || !strings.Contains(err.Error(), "no id") { 249 t.Errorf("FindAndHash: want nil, [32]byte{}, no id specified, got %v, %v, %v", matches, hash, err) 250 } 251} 252