blob: d4b55088a5b8cec26e4c8b60dcc83b9226632830 [file]
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package task
import (
"context"
"errors"
"net/mail"
"path"
"path/filepath"
"reflect"
"strings"
"testing"
"golang.org/x/build/gerrit"
wf "golang.org/x/build/internal/workflow"
)
type fakePrivXGerrit struct {
*FakeGerrit
changes map[string]*gerrit.ChangeInfo
}
func (g *fakePrivXGerrit) GetChange(_ context.Context, changeID string, _ ...gerrit.QueryChangesOpt) (*gerrit.ChangeInfo, error) {
ci, ok := g.changes[changeID]
if !ok {
return nil, errors.New("GetChange: not found")
}
return ci, nil
}
func (g *fakePrivXGerrit) GetRevisionActions(_ context.Context, changeID string, revision string) (map[string]*gerrit.ActionInfo, error) {
if _, ok := g.changes[changeID]; !ok {
return nil, nil
}
return map[string]*gerrit.ActionInfo{
"submit": {Enabled: true},
}, nil
}
func (g *fakePrivXGerrit) MoveChange(ctx context.Context, changeID string, branch string) (gerrit.ChangeInfo, error) {
ci, ok := g.changes[changeID]
if !ok {
return gerrit.ChangeInfo{}, errors.New("MoveChange: not found")
}
ci.Branch = branch
return *ci, nil
}
func (g *fakePrivXGerrit) RebaseChange(ctx context.Context, changeID string, baseRev string) (gerrit.ChangeInfo, error) {
ci, ok := g.changes[changeID]
if !ok {
return gerrit.ChangeInfo{}, errors.New("RebaseChange: not found")
}
return *ci, nil
}
func (g *fakePrivXGerrit) SubmitChange(ctx context.Context, changeID string) (gerrit.ChangeInfo, error) {
ci, ok := g.changes[changeID]
if !ok {
return gerrit.ChangeInfo{}, errors.New("SubmitChange: not found")
}
ci.Status = gerrit.ChangeStatusMerged
return *ci, nil
}
const privXMilestoneYAML = `id: 88810010
security_patches:
- id: 10001
package: golang.org/x/net/http2
track: PRIVATE
changelists:
- https://go-internal-review.git.corp.google.com/c/net/+/1111
- https://go-internal-review.git.corp.google.com/c/net/+/2222
release_note: |
net/http2: turbulence in the frame buffers causes gophers to levitate.
Sending a specially crafted SETTINGS frame with the
ENABLE_LEVITATION=1 causes all subsequent gophers to
float indefinitely.
Thanks to a very levitated gopher for reporting this issue.
This is CVE-1970-0001 and Go issue https://go.dev/issue/4294967296.
cve: CVE-1970-0001
github_issue_id: 4294967296
credits:
- a very levitated gopher
- id: 10002
package: golang.org/x/net/html
track: PUBLIC
changelists:
- https://go.dev/cl/3333
release_note: |
net/html: tokenizer emits poetry instead of tokens under a full moon.
When the system clock aligns with a lunar cycle, the HTML
tokenizer replaces all div elements with haikus about the
Go garbage collector.
Thanks to a confused poet for reporting this issue.
This is CVE-1970-0002 and Go issue https://go.dev/issue/4294967297.
cve: CVE-1970-0002
github_issue_id: 4294967297
credits:
- a confused poet`
func TestPrivXPatch(t *testing.T) {
netRepo := NewFakeRepo(t, "net")
smRepo := NewFakeRepo(t, "security-metadata")
head := smRepo.History()[0]
smRepo.Branch("main", head)
smRepo.CommitOnBranch("main", map[string]string{
path.Join("data", "milestones", "88810010.yaml"): privXMilestoneYAML,
})
netHead := netRepo.History()[0]
netRepo.Branch("public", netHead)
privCommit := netRepo.CommitOnBranch("master", map[string]string{"fix.go": "package fix"})
netRepo.runGit("update-ref", "refs/changes/1111/1", privCommit)
privCommit2 := netRepo.CommitOnBranch("master", map[string]string{"fix2.go": "package fix"})
netRepo.runGit("update-ref", "refs/changes/2222/1", privCommit2)
privCommit3 := netRepo.CommitOnBranch("master", map[string]string{"fix3.go": "package fix"})
netRepo.runGit("update-ref", "refs/changes/3333/1", privCommit3)
privGerrit := &fakePrivXGerrit{
FakeGerrit: NewFakeGerrit(t, netRepo, smRepo),
changes: map[string]*gerrit.ChangeInfo{
"1111": {
ID: "1111",
ChangeID: "1111",
ChangeNumber: 1111,
Project: "net",
Branch: "public",
Submittable: true,
CurrentRevision: "rev1111",
Status: gerrit.ChangeStatusMerged,
Revisions: map[string]gerrit.RevisionInfo{
"rev1111": {
Fetch: map[string]*gerrit.FetchInfo{
"http": {
URL: netRepo.dir.dir,
Ref: "refs/changes/1111/1",
},
},
},
},
},
"2222": {
ID: "2222",
ChangeID: "2222",
ChangeNumber: 2222,
Project: "net",
Branch: "public",
Submittable: true,
CurrentRevision: "rev2222",
Status: gerrit.ChangeStatusMerged,
Revisions: map[string]gerrit.RevisionInfo{
"rev2222": {
Fetch: map[string]*gerrit.FetchInfo{
"http": {
URL: netRepo.dir.dir,
Ref: "refs/changes/2222/1",
},
},
},
},
},
"3333": {
ID: "3333",
ChangeID: "3333",
ChangeNumber: 3333,
Project: "net",
Branch: "public",
Submittable: true,
CurrentRevision: "rev3333",
Status: gerrit.ChangeStatusMerged,
Revisions: map[string]gerrit.RevisionInfo{
"rev3333": {
Fetch: map[string]*gerrit.FetchInfo{
"http": {
URL: netRepo.dir.dir,
Ref: "refs/changes/3333/1",
},
},
},
},
},
},
}
pubRepo := NewFakeRepo(t, "net")
pubRepo.CommitOnBranch("master", map[string]string{"go.mod": "module golang.org/x/net\n\ngo 1.24"})
pubRepo.runGit("tag", "v1.0.0")
pubRepo.SetHook("post-receive", `#!/bin/bash -eu
read old new refname
git update-ref refs/heads/master "$new"
echo "Resolving deltas: 100% (5/5)"
echo "Waiting for private key checker: 1/1 objects left"
echo "Processing changes: refs: 1, new: 1, done"
echo
echo "SUCCESS"
echo
echo " https://go-review.googlesource.com/c/net/+/558675 some change [NEW]"
echo`)
pubBase, _ := strings.CutSuffix(pubRepo.dir.dir, filepath.Base(pubRepo.dir.dir))
pubGerrit := NewFakeGerrit(t, pubRepo)
pubGerrit.ConsiderChangeSubmitted(pubRepo, "558675")
var announcementHeader MailHeader
var announcementMessage MailContent
p := &PrivXPatch{
Git: &Git{},
PrivateGerrit: privGerrit,
PublicGerrit: pubGerrit,
PublicRepoURL: func(repo string) string {
return pubBase + "/" + repo
},
ApproveAction: func(*wf.TaskContext) error { return nil },
SendMail: func(_ *wf.TaskContext, mh MailHeader, mc MailContent) error {
announcementHeader, announcementMessage = mh, mc
return nil
},
AnnounceMailHeader: MailHeader{
From: mail.Address{Address: "security@golang.org"},
To: mail.Address{Address: "golang-announce@googlegroups.com"},
},
}
tagxGerrit := NewFakeGerrit(t, pubRepo)
wd := p.NewDefinition(&TagXReposTasks{Gerrit: tagxGerrit})
w, err := wf.Start(wd, map[string]any{
"Release Milestone": "88810010",
reviewersParam.Name: []string{},
"Repository name": "net",
"Skip post submit result (optional)": true,
})
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
_, err = w.Run(&wf.TaskContext{Context: ctx, Logger: &testLogger{t: t}}, &verboseListener{t: t})
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(announcementHeader, p.AnnounceMailHeader) {
t.Errorf("announcement header: got %#v, want %#v", announcementHeader, p.AnnounceMailHeader)
}
wantSubject := `[security] Vulnerabilities in golang.org/x/net`
if announcementMessage.Subject != wantSubject {
t.Errorf("announcement subject:\ngot %q\nwant %q", announcementMessage.Subject, wantSubject)
}
wantText := `Hello gophers,
We have tagged version v1.1.0 of golang.org/x/net in order to address the following security issues:
net/http2: turbulence in the frame buffers causes gophers to levitate.
Sending a specially crafted SETTINGS frame with the
ENABLE_LEVITATION=1 causes all subsequent gophers to
float indefinitely.
Thanks to a very levitated gopher for reporting this issue.
This is CVE-1970-0001 and Go issue https://go.dev/issue/4294967296.
net/html: tokenizer emits poetry instead of tokens under a full moon.
When the system clock aligns with a lunar cycle, the HTML
tokenizer replaces all div elements with haikus about the
Go garbage collector.
Thanks to a confused poet for reporting this issue.
This is CVE-1970-0002 and Go issue https://go.dev/issue/4294967297.
Cheers,
Go Security team
`
if announcementMessage.BodyText != wantText {
t.Errorf("announcement text:\ngot:\n%s\nwant:\n%s", announcementMessage.BodyText, wantText)
}
wantHTML := `<p>Hello gophers,</p>
<p>We have tagged version v1.1.0 of golang.org/x/net in order to address the following security issues:</p>
<p>net/http2: turbulence in the frame buffers causes gophers to levitate.</p>
<p>Sending a specially crafted SETTINGS frame with the<br>
ENABLE_LEVITATION=1 causes all subsequent gophers to<br>
float indefinitely.</p>
<p>Thanks to a very levitated gopher for reporting this issue.</p>
<p>This is CVE-1970-0001 and Go issue <a href="https://go.dev/issue/4294967296">https://go.dev/issue/4294967296</a>.</p>
<p>net/html: tokenizer emits poetry instead of tokens under a full moon.</p>
<p>When the system clock aligns with a lunar cycle, the HTML<br>
tokenizer replaces all div elements with haikus about the<br>
Go garbage collector.</p>
<p>Thanks to a confused poet for reporting this issue.</p>
<p>This is CVE-1970-0002 and Go issue <a href="https://go.dev/issue/4294967297">https://go.dev/issue/4294967297</a>.</p>
<p>Cheers,<br>
Go Security team</p>
`
if announcementMessage.BodyHTML != wantHTML {
t.Errorf("announcement HTML:\ngot:\n%s\nwant:\n%s", announcementMessage.BodyHTML, wantHTML)
}
}
func TestRepoName(t *testing.T) {
tests := []struct {
name string
pkg string
want string
wantErr bool
}{
{"subpackage", "golang.org/x/net/http2", "net", false},
{"root module", "golang.org/x/net", "net", false},
{"different repo", "golang.org/x/crypto/ssh", "crypto", false},
{"non-x module", "github.com/foo/bar", "", true},
{"trailing slash", "golang.org/x/", "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := repoName(tt.pkg)
if (err != nil) != tt.wantErr {
t.Errorf("repoName(%q): err = %v, wantErr = %v", tt.pkg, err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("repoName(%q) = %q, want %q", tt.pkg, got, tt.want)
}
})
}
}