Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

packfile: Avoid panics patching corrupted deltas. #492

Merged
merged 1 commit into from
Jul 19, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions plumbing/format/packfile/delta_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package packfile

import (
"fmt"
"math/rand"

. "gopkg.in/check.v1"
Expand Down Expand Up @@ -86,9 +85,28 @@ func (s *DeltaSuite) TestAddDelta(c *C) {
baseBuf := genBytes(t.base)
targetBuf := genBytes(t.target)
delta := DiffDelta(baseBuf, targetBuf)
result := PatchDelta(baseBuf, delta)
result, err := PatchDelta(baseBuf, delta)

c.Log(fmt.Printf("Executing test case: %s\n", t.description))
c.Log("Executing test case:", t.description)
c.Assert(err, IsNil)
c.Assert(result, DeepEquals, targetBuf)
}
}

func (s *DeltaSuite) TestIncompleteDelta(c *C) {
for _, t := range s.testCases {
c.Log("Incomplete delta on:", t.description)
baseBuf := genBytes(t.base)
targetBuf := genBytes(t.target)
delta := DiffDelta(baseBuf, targetBuf)
delta = delta[:len(delta)-2]
result, err := PatchDelta(baseBuf, delta)
c.Assert(err, NotNil)
c.Assert(result, IsNil)
}

// check nil input too
result, err := PatchDelta(nil, nil)
c.Assert(err, NotNil)
c.Assert(result, IsNil)
}
77 changes: 64 additions & 13 deletions plumbing/format/packfile/patch_delta.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package packfile

import (
"errors"
"io/ioutil"

"gopkg.in/src-d/go-git.v4/plumbing"
Expand Down Expand Up @@ -30,7 +31,11 @@ func ApplyDelta(target, base plumbing.EncodedObject, delta []byte) error {
return err
}

dst := PatchDelta(src, delta)
dst, err := PatchDelta(src, delta)
if err != nil {
return err
}

target.SetSize(int64(len(dst)))

if _, err := w.Write(dst); err != nil {
Expand All @@ -40,15 +45,22 @@ func ApplyDelta(target, base plumbing.EncodedObject, delta []byte) error {
return nil
}

var (
ErrInvalidDelta = errors.New("invalid delta")
ErrDeltaCmd = errors.New("wrong delta command")
)

// PatchDelta returns the result of applying the modification deltas in delta to src.
func PatchDelta(src, delta []byte) []byte {
// An error will be returned if delta is corrupted (ErrDeltaLen) or an action command
// is not copy from source or copy from delta (ErrDeltaCmd).
func PatchDelta(src, delta []byte) ([]byte, error) {
if len(delta) < deltaSizeMin {
return nil
return nil, ErrInvalidDelta
}

srcSz, delta := decodeLEB128(delta)
if srcSz != uint(len(src)) {
return nil
return nil, ErrInvalidDelta
}

targetSz, delta := decodeLEB128(delta)
Expand All @@ -57,12 +69,25 @@ func PatchDelta(src, delta []byte) []byte {
var dest []byte
var cmd byte
for {
if len(delta) == 0 {
return nil, ErrInvalidDelta
}

cmd = delta[0]
delta = delta[1:]
if isCopyFromSrc(cmd) {
var offset, sz uint
offset, delta = decodeOffset(cmd, delta)
sz, delta = decodeSize(cmd, delta)
var err error
offset, delta, err = decodeOffset(cmd, delta)
if err != nil {
return nil, err
}

sz, delta, err = decodeSize(cmd, delta)
if err != nil {
return nil, err
}

if invalidSize(sz, targetSz) ||
invalidOffsetSize(offset, sz, srcSz) {
break
Expand All @@ -72,21 +97,26 @@ func PatchDelta(src, delta []byte) []byte {
} else if isCopyFromDelta(cmd) {
sz := uint(cmd) // cmd is the size itself
if invalidSize(sz, targetSz) {
break
return nil, ErrInvalidDelta
}

if uint(len(delta)) < sz {
return nil, ErrInvalidDelta
}

dest = append(dest, delta[0:sz]...)
remainingTargetSz -= sz
delta = delta[sz:]
} else {
return nil
return nil, ErrDeltaCmd
}

if remainingTargetSz <= 0 {
break
}
}

return dest
return dest, nil
}

// Decodes a number encoded as an unsigned LEB128 at the start of some
Expand Down Expand Up @@ -124,47 +154,68 @@ func isCopyFromDelta(cmd byte) bool {
return (cmd&0x80) == 0 && cmd != 0
}

func decodeOffset(cmd byte, delta []byte) (uint, []byte) {
func decodeOffset(cmd byte, delta []byte) (uint, []byte, error) {
var offset uint
if (cmd & 0x01) != 0 {
if len(delta) == 0 {
return 0, nil, ErrInvalidDelta
}
offset = uint(delta[0])
delta = delta[1:]
}
if (cmd & 0x02) != 0 {
if len(delta) == 0 {
return 0, nil, ErrInvalidDelta
}
offset |= uint(delta[0]) << 8
delta = delta[1:]
}
if (cmd & 0x04) != 0 {
if len(delta) == 0 {
return 0, nil, ErrInvalidDelta
}
offset |= uint(delta[0]) << 16
delta = delta[1:]
}
if (cmd & 0x08) != 0 {
if len(delta) == 0 {
return 0, nil, ErrInvalidDelta
}
offset |= uint(delta[0]) << 24
delta = delta[1:]
}

return offset, delta
return offset, delta, nil
}

func decodeSize(cmd byte, delta []byte) (uint, []byte) {
func decodeSize(cmd byte, delta []byte) (uint, []byte, error) {
var sz uint
if (cmd & 0x10) != 0 {
if len(delta) == 0 {
return 0, nil, ErrInvalidDelta
}
sz = uint(delta[0])
delta = delta[1:]
}
if (cmd & 0x20) != 0 {
if len(delta) == 0 {
return 0, nil, ErrInvalidDelta
}
sz |= uint(delta[0]) << 8
delta = delta[1:]
}
if (cmd & 0x40) != 0 {
if len(delta) == 0 {
return 0, nil, ErrInvalidDelta
}
sz |= uint(delta[0]) << 16
delta = delta[1:]
}
if sz == 0 {
sz = 0x10000
}

return sz, delta
return sz, delta, nil
}

func invalidSize(sz, targetSz uint) bool {
Expand Down