Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hash/crc32: panic on arm64 with go1.21.0 when indexing slice #62131

Closed
Slessi opened this issue Aug 18, 2023 · 15 comments
Closed

hash/crc32: panic on arm64 with go1.21.0 when indexing slice #62131

Slessi opened this issue Aug 18, 2023 · 15 comments
Assignees
Labels
arch-arm64 compiler/runtime Issues related to the Go compiler and/or runtime. NeedsFix The path to resolution is known, but the work has not been done.
Milestone

Comments

@Slessi
Copy link

Slessi commented Aug 18, 2023

What version of Go are you using (go version)?

$ go version
go version go1.21.0 darwin/arm64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=''
GOARCH='arm64'
GOBIN=''
GOCACHE='/Users/edward/Library/Caches/go-build'
GOENV='/Users/edward/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMODCACHE='/Users/edward/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/edward/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/opt/homebrew/Cellar/go/1.21.0/libexec'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/opt/homebrew/Cellar/go/1.21.0/libexec/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.21.0'
GCCGO='gccgo'
AR='ar'
CC='cc'
CXX='c++'
CGO_ENABLED='1'
GOMOD='/dev/null'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/ld/l69fjx9x3gl6qfx056xt89hr0000gp/T/go-build4293017448=/tmp/go-build -gno-record-gcc-switches -fno-common'

What did you do?

This code panics on go 1.21 on arm64 but runs fine on other platforms or go 1.20 arm64:

It doesn't occur if I add a fmt.Println(index) or debug the application, before calling colors[index]

package main

import (
	"fmt"
	"hash/crc32"
)

func pickColor(userID string) string {
	colors := []string{
		"ffd179",
		"f388a8",
		"b786d2",
		"bed3f4",
		"88a0f3",
		"6fe8d9",
		"e86f85",
	}
	hash := crc32.ChecksumIEEE([]byte(userID))
	index := hash % uint32(len(colors))
	return colors[index]
}

func main() {
	fmt.Println(pickColor("jHjDiYOWTOS53K9t3XbOU7QXAwD3"))
}

What did you expect to see?

A string printed

What did you see instead?

panic: runtime error: index out of range [-4294967295]

goroutine 1 [running]:
main.pickColor({0x10494b15b?, 0x0?})
        /Users/edward/Desktop/go_bug.go:20 +0x118
main.main()
        /Users/edward/Desktop/go_bug.go:24 +0x28
exit status 2
@Slessi Slessi changed the title affected/package: Panic on ARM with go1.21 when indexing with uint32 Aug 18, 2023
@ubombi
Copy link

ubombi commented Aug 18, 2023

Smaller example

package main

import "hash/crc32"

func main() {
	colors := []string{"ffd179", "b786d2", "bed3f4"}

	// has is uint32(754903878), but panic occures only during crc32 call
	// and not, when you set this value explicitely
	hash := crc32.ChecksumIEEE([]byte("jHjDiYOWTOS53K9t3XbOU7QXAwD3"))
	index := hash % uint32(3)

	_ = colors[index] // PANIC: index out of range -4294967296
	return
}

The magic uint64 number -4294967296 is 0xFFFFFFFF 00000000, but all variables are 32bit unsigned ints. Thus
dropping high bits, shall be 0 or 0x00000000, however, due to optimizations, go compares -4294967296 with array length and panics.

More context

during execution, around index := hash % uint32(len(colors)) :

<main.main+108> sub x0, x0, x1

x0: 0xFFFFFFFF 2CFEEB46 -- register, which would be used as array index is .
x1: 0x2CFEEB46 -- (754903878) value needed for reminder calculation.

After substracting x1 from x0 part, we end with 0xFFFFFFFF00000000, where previous XORing set high bit's to 1.
As result out of bounds check fails, comparing remainder with array length fails and jumps to Panic.

uint32 type is ignored here. Everywhere.

Panics on go1.21.0 linux/arm64
Works on go1.21.0 linux/amd64

@Slessi Slessi changed the title Panic on ARM with go1.21 when indexing with uint32 hash/crc32: Panic on ARM with go1.21 when indexing slice Aug 18, 2023
@ubombi
Copy link

ubombi commented Aug 18, 2023

According to docs & playground proof:

^x bitwise complement is m ^ x with m = "all bits set to 1" for unsigned x and m = -1 for signed x

Following call makes sets x0 to -1, effectively 0xfffffffffffffff

return ^ieeeUpdate(^crc, p)     // hash/crc32.archUpdateIEEE mvn x0 x0

@dmitshur
Copy link
Contributor

CC @golang/security.

@dmitshur dmitshur added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Aug 18, 2023
@dmitshur dmitshur added this to the Backlog milestone Aug 18, 2023
@ubombi
Copy link

ubombi commented Aug 18, 2023

No dependencies example

./bug.s

#include "textflag.h"

// func simple(crc uint32, p []byte) uint32
TEXT ·simple(SB),NOSPLIT,$0-36
	MOVWU	crc+0(FP), R9  // CRC value
	MOVWU	R9, ret+32(FP)
	RET

./bug.go

package main

func simple(crc uint32, p []byte) uint32

func main() {
	colors := []string{ "", "", ""}
	hash := archUpdateIEEE(0, []byte("jHjDiYOWTOS53K9t3XbOU7QXAwD3"))
	index := hash % uint32(3)
	_ = colors[index]
	return
}

func archUpdateIEEE(crc uint32, p []byte) uint32 {
	return ^simple(^crc, p)
}

@bcmills bcmills added the compiler/runtime Issues related to the Go compiler and/or runtime. label Aug 18, 2023
@bcmills
Copy link
Contributor

bcmills commented Aug 18, 2023

Since you can reproduce the problem in assembly, it seems likely that this is a compiler or runtime change.

Can you build the Go repo from source and use git bisect to find the exact commit at which the regression began?

@bcmills bcmills removed this from the Backlog milestone Aug 18, 2023
@bcmills
Copy link
Contributor

bcmills commented Aug 18, 2023

(attn @golang/compiler @golang/arm)

@dmitshur dmitshur added this to the Backlog milestone Aug 18, 2023
@randall77
Copy link
Contributor

Simple reproducer:

package main

//go:noinline
func f() uint32 {
	return x - y
}
var x uint32 = 0
var y uint32 = 1
var z [2]int

func main() {
	_ = z[f() % 3]
}

Looks like there is a missing MOVWU in the % computation in 1.21.

@randall77
Copy link
Contributor

@randall77
Copy link
Contributor

Looks like the MOVWUreg is removed when its arg is a MSUBW, which is guaranteed to zero the upper 32 bits. But then later that arg gets changed to something that doesn't zero the upper 32 bits, a SUB.
So I think the logic that we don't need the zero extension if its arg does the zero extension itself, is invalid if we also have the rule that rewrites of 32-bit-typed values can change the upper 32 bits.
The rule is only valid if all our rewrite rules preserve the upper-32-bits-are-zeroed property.
Unless we do the MOVWUreg removal in a separate pass after all other rewrites are done.

@randall77
Copy link
Contributor

The rule is only valid if all our rewrite rules preserve the upper-32-bits-are-zeroed property.

I've hacked up a change that fixes all the rules to preserve the semantics of the upper 32 bits. For example, when rewriting MULW with constant -1 argument, use NEGW and not NEG.
It's a pretty rote change but kind of large. Not something I would want to backport. I think we should revert CL 427454 on the 1.21 release branch and do my change for 1.22.

I worry other architectures have the same issue, as lots of those were started by copying the arm64 port. I will investigate.

@randall77
Copy link
Contributor

@gopherbot please open a backport to 1.21.

@gopherbot
Copy link

Backport issue(s) opened: #62143 (for 1.21).

Remember to create the cherry-pick CL(s) as soon as the patch is submitted to master, according to https://go.dev/wiki/MinorReleases.

@gopherbot
Copy link

Change https://go.dev/cl/520916 mentions this issue: cmd/compile: ensure we keep top 32 bits zeroed for 32-bit arm64 ops

@dmitshur dmitshur changed the title hash/crc32: Panic on ARM with go1.21 when indexing slice hash/crc32: panic on arm64 with go1.21.0 when indexing slice Aug 19, 2023
@dmitshur dmitshur added NeedsFix The path to resolution is known, but the work has not been done. and removed NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. labels Aug 19, 2023
@RuinanSun
Copy link
Contributor

Thanks for the fix! @randall77

I've hacked up a change that fixes all the rules to preserve the semantics of the upper 32 bits.

Does it look like CL 427454 is still correct if we can hold this for all 32-bit rewrite rules?

The revert is fine, I just wonder if we can redo this work after your fix in 1.22.

@erifan
Copy link

erifan commented Aug 21, 2023

The revert is fine, I just wonder if we can redo this work after your fix in 1.22.

CL 427454 is not reverted in the tip branch. I think Keith's fix is the right direction.

cellularmitosis pushed a commit to cellularmitosis/go that referenced this issue Aug 24, 2023
When rewriting, for example, MSUBW, we need to ensure that the result
has its 32 top bits zeroed. That's what the instruction is spec'd to do.
Normally, we'd only use MSUBW for computations on 32-bit values, and
as such the top 32 bits aren't normally used. But some situations, like
if we cast the result to a uint64, the top 32 bits do matter.

This comes up in 62131 because we have a rule saying, MOVWUreg applied
to a MSUBW is unnecessary, as the arg to MOVWUreg already has zeroed
top 32 bits. But if MSUBW is later rewritten to another op that doesn't
zero the top 32 bits (SUB, probably), getting rid of the MOVWUreg earlier
causes a problem.

So change rewrite rules to always maintain the top 32 bits as zero if the
instruction is spec'd to provide that. We need to introduce a few *W operations
to make that happen.

Fixes golang#62131

Change-Id: If3d160821e285fd7454746b735a243671bff8894
Reviewed-on: https://go-review.googlesource.com/c/go/+/520916
Run-TryBot: Keith Randall <khr@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Keith Randall <khr@google.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
arch-arm64 compiler/runtime Issues related to the Go compiler and/or runtime. NeedsFix The path to resolution is known, but the work has not been done.
Projects
None yet
Development

No branches or pull requests

8 participants