Skip to content

Commit c78ae57

Browse files
authored
Fix a potential use-after-free in DiffNotifyCallback (libgit2#579)
This change makes the DiffNotifyCallback always use an "unowned" *git.Diff that does _not_ run the finalizer. Since the underlying git_diff object is still owned by libgit2, we shouldn't be calling Diff.Free() on it, even by accident, since that would cause a whole lot of undefined behavior. Now instead of storing a reference to a *git.Diff in the intermediate state while the diff operation is being done, create a brand new *git.Diff for every callback invocation, and only create a fully-owned *git.Diff when the diff operation is done and the ownership is transfered to Go.
1 parent 619a9c2 commit c78ae57

File tree

2 files changed

+40
-40
lines changed

2 files changed

+40
-40
lines changed

diff.go

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,9 @@ func diffLineFromC(line *C.git_diff_line) DiffLine {
131131
}
132132

133133
type Diff struct {
134-
ptr *C.git_diff
135-
repo *Repository
134+
ptr *C.git_diff
135+
repo *Repository
136+
runFinalizer bool
136137
}
137138

138139
func (diff *Diff) NumDeltas() (int, error) {
@@ -165,8 +166,9 @@ func newDiffFromC(ptr *C.git_diff, repo *Repository) *Diff {
165166
}
166167

167168
diff := &Diff{
168-
ptr: ptr,
169-
repo: repo,
169+
ptr: ptr,
170+
repo: repo,
171+
runFinalizer: true,
170172
}
171173

172174
runtime.SetFinalizer(diff, (*Diff).Free)
@@ -177,6 +179,11 @@ func (diff *Diff) Free() error {
177179
if diff.ptr == nil {
178180
return ErrInvalid
179181
}
182+
if !diff.runFinalizer {
183+
// This is the case with the Diff objects that are involved in the DiffNotifyCallback.
184+
diff.ptr = nil
185+
return nil
186+
}
180187
runtime.SetFinalizer(diff, nil)
181188
C.git_diff_free(diff.ptr)
182189
diff.ptr = nil
@@ -579,9 +586,9 @@ var (
579586
)
580587

581588
type diffNotifyData struct {
582-
Callback DiffNotifyCallback
583-
Diff *Diff
584-
Error error
589+
Callback DiffNotifyCallback
590+
Repository *Repository
591+
Error error
585592
}
586593

587594
//export diffNotifyCb
@@ -595,11 +602,20 @@ func diffNotifyCb(_diff_so_far unsafe.Pointer, delta_to_add *C.git_diff_delta, m
595602
}
596603

597604
if data != nil {
598-
if data.Diff == nil {
599-
data.Diff = newDiffFromC(diff_so_far, nil)
605+
// We are not taking ownership of this diff pointer, so no finalizer is set.
606+
diff := &Diff{
607+
ptr: diff_so_far,
608+
repo: data.Repository,
609+
runFinalizer: false,
600610
}
601611

602-
err := data.Callback(data.Diff, diffDeltaFromC(delta_to_add), C.GoString(matched_pathspec))
612+
err := data.Callback(diff, diffDeltaFromC(delta_to_add), C.GoString(matched_pathspec))
613+
614+
// Since the callback could theoretically keep a reference to the diff
615+
// (which could be freed by libgit2 if an error occurs later during the
616+
// diffing process), this converts a use-after-free (terrible!) into a nil
617+
// dereference ("just" pretty bad).
618+
diff.ptr = nil
603619

604620
if err == ErrDeltaSkip {
605621
return 1
@@ -613,11 +629,12 @@ func diffNotifyCb(_diff_so_far unsafe.Pointer, delta_to_add *C.git_diff_delta, m
613629
return 0
614630
}
615631

616-
func diffOptionsToC(opts *DiffOptions) (copts *C.git_diff_options, notifyData *diffNotifyData) {
632+
func diffOptionsToC(opts *DiffOptions, repo *Repository) (copts *C.git_diff_options) {
617633
cpathspec := C.git_strarray{}
618634
if opts != nil {
619-
notifyData = &diffNotifyData{
620-
Callback: opts.NotifyCallback,
635+
notifyData := &diffNotifyData{
636+
Callback: opts.NotifyCallback,
637+
Repository: repo,
621638
}
622639

623640
if opts.Pathspec != nil {
@@ -670,7 +687,7 @@ func (v *Repository) DiffTreeToTree(oldTree, newTree *Tree, opts *DiffOptions) (
670687
newPtr = newTree.cast_ptr
671688
}
672689

673-
copts, notifyData := diffOptionsToC(opts)
690+
copts := diffOptionsToC(opts, v)
674691
defer freeDiffOptions(copts)
675692

676693
runtime.LockOSThread()
@@ -682,10 +699,6 @@ func (v *Repository) DiffTreeToTree(oldTree, newTree *Tree, opts *DiffOptions) (
682699
if ecode < 0 {
683700
return nil, MakeGitError(ecode)
684701
}
685-
686-
if notifyData != nil && notifyData.Diff != nil {
687-
return notifyData.Diff, nil
688-
}
689702
return newDiffFromC(diffPtr, v), nil
690703
}
691704

@@ -697,7 +710,7 @@ func (v *Repository) DiffTreeToWorkdir(oldTree *Tree, opts *DiffOptions) (*Diff,
697710
oldPtr = oldTree.cast_ptr
698711
}
699712

700-
copts, notifyData := diffOptionsToC(opts)
713+
copts := diffOptionsToC(opts, v)
701714
defer freeDiffOptions(copts)
702715

703716
runtime.LockOSThread()
@@ -708,10 +721,6 @@ func (v *Repository) DiffTreeToWorkdir(oldTree *Tree, opts *DiffOptions) (*Diff,
708721
if ecode < 0 {
709722
return nil, MakeGitError(ecode)
710723
}
711-
712-
if notifyData != nil && notifyData.Diff != nil {
713-
return notifyData.Diff, nil
714-
}
715724
return newDiffFromC(diffPtr, v), nil
716725
}
717726

@@ -728,7 +737,7 @@ func (v *Repository) DiffTreeToIndex(oldTree *Tree, index *Index, opts *DiffOpti
728737
indexPtr = index.ptr
729738
}
730739

731-
copts, notifyData := diffOptionsToC(opts)
740+
copts := diffOptionsToC(opts, v)
732741
defer freeDiffOptions(copts)
733742

734743
runtime.LockOSThread()
@@ -740,10 +749,6 @@ func (v *Repository) DiffTreeToIndex(oldTree *Tree, index *Index, opts *DiffOpti
740749
if ecode < 0 {
741750
return nil, MakeGitError(ecode)
742751
}
743-
744-
if notifyData != nil && notifyData.Diff != nil {
745-
return notifyData.Diff, nil
746-
}
747752
return newDiffFromC(diffPtr, v), nil
748753
}
749754

@@ -755,7 +760,7 @@ func (v *Repository) DiffTreeToWorkdirWithIndex(oldTree *Tree, opts *DiffOptions
755760
oldPtr = oldTree.cast_ptr
756761
}
757762

758-
copts, notifyData := diffOptionsToC(opts)
763+
copts := diffOptionsToC(opts, v)
759764
defer freeDiffOptions(copts)
760765

761766
runtime.LockOSThread()
@@ -766,10 +771,6 @@ func (v *Repository) DiffTreeToWorkdirWithIndex(oldTree *Tree, opts *DiffOptions
766771
if ecode < 0 {
767772
return nil, MakeGitError(ecode)
768773
}
769-
770-
if notifyData != nil && notifyData.Diff != nil {
771-
return notifyData.Diff, nil
772-
}
773774
return newDiffFromC(diffPtr, v), nil
774775
}
775776

@@ -781,7 +782,7 @@ func (v *Repository) DiffIndexToWorkdir(index *Index, opts *DiffOptions) (*Diff,
781782
indexPtr = index.ptr
782783
}
783784

784-
copts, notifyData := diffOptionsToC(opts)
785+
copts := diffOptionsToC(opts, v)
785786
defer freeDiffOptions(copts)
786787

787788
runtime.LockOSThread()
@@ -792,10 +793,6 @@ func (v *Repository) DiffIndexToWorkdir(index *Index, opts *DiffOptions) (*Diff,
792793
if ecode < 0 {
793794
return nil, MakeGitError(ecode)
794795
}
795-
796-
if notifyData != nil && notifyData.Diff != nil {
797-
return notifyData.Diff, nil
798-
}
799796
return newDiffFromC(diffPtr, v), nil
800797
}
801798

@@ -819,20 +816,23 @@ func DiffBlobs(oldBlob *Blob, oldAsPath string, newBlob *Blob, newAsPath string,
819816
handle := pointerHandles.Track(data)
820817
defer pointerHandles.Untrack(handle)
821818

819+
var repo *Repository
822820
var oldBlobPtr, newBlobPtr *C.git_blob
823821
if oldBlob != nil {
824822
oldBlobPtr = oldBlob.cast_ptr
823+
repo = oldBlob.repo
825824
}
826825
if newBlob != nil {
827826
newBlobPtr = newBlob.cast_ptr
827+
repo = newBlob.repo
828828
}
829829

830830
oldBlobPath := C.CString(oldAsPath)
831831
defer C.free(unsafe.Pointer(oldBlobPath))
832832
newBlobPath := C.CString(newAsPath)
833833
defer C.free(unsafe.Pointer(newBlobPath))
834834

835-
copts, _ := diffOptionsToC(opts)
835+
copts := diffOptionsToC(opts, repo)
836836
defer freeDiffOptions(copts)
837837

838838
runtime.LockOSThread()

patch.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func (v *Repository) PatchFromBuffers(oldPath, newPath string, oldBuf, newBuf []
7777
cNewPath := C.CString(newPath)
7878
defer C.free(unsafe.Pointer(cNewPath))
7979

80-
copts, _ := diffOptionsToC(opts)
80+
copts := diffOptionsToC(opts, v)
8181
defer freeDiffOptions(copts)
8282

8383
runtime.LockOSThread()

0 commit comments

Comments
 (0)