Source file src/cmd/internal/buildid/buildid_test.go

     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  
     5  package buildid
     6  
     7  import (
     8  	"bytes"
     9  	"crypto/sha256"
    10  	"debug/elf"
    11  	"encoding/binary"
    12  	"internal/obscuretestdata"
    13  	"os"
    14  	"reflect"
    15  	"strings"
    16  	"testing"
    17  )
    18  
    19  const (
    20  	expectedID = "abcdefghijklmnopqrstuvwxyz.1234567890123456789012345678901234567890123456789012345678901234"
    21  	newID      = "bcdefghijklmnopqrstuvwxyza.2345678901234567890123456789012345678901234567890123456789012341"
    22  )
    23  
    24  func 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 Readefile; 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  
   161  func 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  
   215  func 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  
   245  func 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  

View as plain text