Skip to content

Commit 481eedc

Browse files
dsnetbradfitz
authored andcommitted
archive/tar: properly format GNU base-256 encoding
Motivation: * Previous implementation silently failed when an integer overflow occurred. Now, we report an ErrFieldTooLong. * Previous implementation did not encode in two's complement format and was unable to encode negative numbers. The relevant GNU specification says: <<< GNU format uses two's-complement base-256 notation to store values that do not fit into standard ustar range. >>> Fixes #12436 Change-Id: I09c20602eabf8ae3a7e0db35b79440a64bfaf807 Reviewed-on: https://go-review.googlesource.com/17425 Reviewed-by: Brad Fitzpatrick <[email protected]> Run-TryBot: Brad Fitzpatrick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent e71d640 commit 481eedc

File tree

2 files changed

+105
-4
lines changed

2 files changed

+105
-4
lines changed

src/archive/tar/writer.go

+22-4
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,31 @@ func (f *formatter) formatOctal(b []byte, x int64) {
9494
f.formatString(b, s)
9595
}
9696

97+
// fitsInBase256 reports whether x can be encoded into n bytes using base-256
98+
// encoding. Unlike octal encoding, base-256 encoding does not require that the
99+
// string ends with a NUL character. Thus, all n bytes are available for output.
100+
//
101+
// If operating in binary mode, this assumes strict GNU binary mode; which means
102+
// that the first byte can only be either 0x80 or 0xff. Thus, the first byte is
103+
// equivalent to the sign bit in two's complement form.
104+
func fitsInBase256(n int, x int64) bool {
105+
var binBits = uint(n-1) * 8
106+
return n >= 9 || (x >= -1<<binBits && x < 1<<binBits)
107+
}
108+
97109
// Write x into b, as binary (GNUtar/star extension).
98110
func (f *formatter) formatNumeric(b []byte, x int64) {
99-
for i := len(b) - 1; x > 0 && i >= 0; i-- {
100-
b[i] = byte(x)
101-
x >>= 8
111+
if fitsInBase256(len(b), x) {
112+
for i := len(b) - 1; i >= 0; i-- {
113+
b[i] = byte(x)
114+
x >>= 8
115+
}
116+
b[0] |= 0x80 // Highest bit indicates binary format
117+
return
102118
}
103-
b[0] |= 0x80 // highest bit indicates binary format
119+
120+
f.formatOctal(b, 0) // Last resort, just write zero
121+
f.err = ErrFieldTooLong
104122
}
105123

106124
var (

src/archive/tar/writer_test.go

+83
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"fmt"
1010
"io"
1111
"io/ioutil"
12+
"math"
1213
"os"
1314
"reflect"
1415
"sort"
@@ -637,3 +638,85 @@ func TestFormatPAXRecord(t *testing.T) {
637638
}
638639
}
639640
}
641+
642+
func TestFitsInBase256(t *testing.T) {
643+
var vectors = []struct {
644+
input int64
645+
width int
646+
ok bool
647+
}{
648+
{+1, 8, true},
649+
{0, 8, true},
650+
{-1, 8, true},
651+
{1 << 56, 8, false},
652+
{(1 << 56) - 1, 8, true},
653+
{-1 << 56, 8, true},
654+
{(-1 << 56) - 1, 8, false},
655+
{121654, 8, true},
656+
{-9849849, 8, true},
657+
{math.MaxInt64, 9, true},
658+
{0, 9, true},
659+
{math.MinInt64, 9, true},
660+
{math.MaxInt64, 12, true},
661+
{0, 12, true},
662+
{math.MinInt64, 12, true},
663+
}
664+
665+
for _, v := range vectors {
666+
ok := fitsInBase256(v.width, v.input)
667+
if ok != v.ok {
668+
t.Errorf("checkNumeric(%d, %d): got %v, want %v", v.input, v.width, ok, v.ok)
669+
}
670+
}
671+
}
672+
673+
func TestFormatNumeric(t *testing.T) {
674+
var vectors = []struct {
675+
input int64
676+
output string
677+
ok bool
678+
}{
679+
// Test base-256 (binary) encoded values.
680+
{-1, "\xff", true},
681+
{-1, "\xff\xff", true},
682+
{-1, "\xff\xff\xff", true},
683+
{(1 << 0), "0", false},
684+
{(1 << 8) - 1, "\x80\xff", true},
685+
{(1 << 8), "0\x00", false},
686+
{(1 << 16) - 1, "\x80\xff\xff", true},
687+
{(1 << 16), "00\x00", false},
688+
{-1 * (1 << 0), "\xff", true},
689+
{-1*(1<<0) - 1, "0", false},
690+
{-1 * (1 << 8), "\xff\x00", true},
691+
{-1*(1<<8) - 1, "0\x00", false},
692+
{-1 * (1 << 16), "\xff\x00\x00", true},
693+
{-1*(1<<16) - 1, "00\x00", false},
694+
{537795476381659745, "0000000\x00", false},
695+
{537795476381659745, "\x80\x00\x00\x00\x07\x76\xa2\x22\xeb\x8a\x72\x61", true},
696+
{-615126028225187231, "0000000\x00", false},
697+
{-615126028225187231, "\xff\xff\xff\xff\xf7\x76\xa2\x22\xeb\x8a\x72\x61", true},
698+
{math.MaxInt64, "0000000\x00", false},
699+
{math.MaxInt64, "\x80\x00\x00\x00\x7f\xff\xff\xff\xff\xff\xff\xff", true},
700+
{math.MinInt64, "0000000\x00", false},
701+
{math.MinInt64, "\xff\xff\xff\xff\x80\x00\x00\x00\x00\x00\x00\x00", true},
702+
{math.MaxInt64, "\x80\x7f\xff\xff\xff\xff\xff\xff\xff", true},
703+
{math.MinInt64, "\xff\x80\x00\x00\x00\x00\x00\x00\x00", true},
704+
}
705+
706+
for _, v := range vectors {
707+
var f formatter
708+
output := make([]byte, len(v.output))
709+
f.formatNumeric(output, v.input)
710+
ok := (f.err == nil)
711+
if ok != v.ok {
712+
if v.ok {
713+
t.Errorf("formatNumeric(%d): got formatting failure, want success", v.input)
714+
} else {
715+
t.Errorf("formatNumeric(%d): got formatting success, want failure", v.input)
716+
}
717+
}
718+
if string(output) != v.output {
719+
t.Errorf("formatNumeric(%d): got %q, want %q", v.input, output, v.output)
720+
}
721+
}
722+
}

0 commit comments

Comments
 (0)