-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Check serialized coroutine state is valid (#101)
When serializing a durable coroutine, the serialized representation is probably only valid for the build that generated it. There are no checks to ensure that the serialized representation of a coroutine is valid for a particular build. When the serialized representation is no longer valid, the program crashes with cryptic error messages. This PR fixes the issue by prefixing the serialized representation with a build hash. If there's a hash mismatch, `Unmarshal` returns an `ErrInvalidState` error that the program can handle gracefully (for example, the program may choose to restart the coroutine). Rather than hashing the binary (which might be large), we extract the Go Build ID and use it as the hash. The build ID changes when the source changes, but also when the compiler and any other dependencies change too (which both may affect the serialized representation of coroutines). I adapted the ELF and Mach-O parsers from https://github.com/golang/go/tree/3803c8588/src/cmd/internal/buildid, but kept it simple and only added a parser for binaries generated by the Go compiler (and not gccgo).
- Loading branch information
Showing
12 changed files
with
199 additions
and
69 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package types | ||
|
||
// buildID is the build identifier for the binary. | ||
var buildID string |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package types | ||
|
||
import ( | ||
"bytes" | ||
"debug/macho" | ||
"os" | ||
"strconv" | ||
) | ||
|
||
func init() { | ||
f, err := macho.Open(os.Args[0]) | ||
if err != nil { | ||
panic("cannot read Mach-O binary: " + err.Error()) | ||
} | ||
defer f.Close() | ||
|
||
initMachOFunctionTables(f) | ||
initMachOBuildID(f) | ||
} | ||
|
||
func initMachOFunctionTables(f *macho.File) { | ||
pclntab := f.Section("__gopclntab") | ||
pclntabData, err := readSection(pclntab, pclntab.Size) | ||
if err != nil { | ||
panic("cannot read pclntab: " + err.Error()) | ||
} | ||
symtab := f.Section("__gosymtab") | ||
symtabData, err := readSection(symtab, symtab.Size) | ||
if err != nil { | ||
panic("cannot read symtab: " + err.Error()) | ||
} | ||
initFunctionTables(pclntabData, symtabData) | ||
} | ||
|
||
func initMachOBuildID(f *macho.File) { | ||
text := f.Section("__text") | ||
|
||
// Read up to 32KB from the text section. | ||
// See https://github.com/golang/go/blob/3803c858/src/cmd/internal/buildid/note.go#L199 | ||
data, err := readSection(text, min(text.Size, 32*1024)) | ||
if err != nil { | ||
panic("cannot read __text: " + err.Error()) | ||
} | ||
|
||
// From https://github.com/golang/go/blob/3803c858/src/cmd/internal/buildid/buildid.go#L300 | ||
i := bytes.Index(data, buildIDPrefix) | ||
if i < 0 { | ||
panic("build ID not found") | ||
} | ||
j := bytes.Index(data[i+len(buildIDPrefix):], buildIDEnd) | ||
if j < 0 { | ||
panic("build ID not found") | ||
} | ||
quoted := data[i+len(buildIDPrefix)-1 : i+len(buildIDPrefix)+j+1] | ||
id, err := strconv.Unquote(string(quoted)) | ||
if err != nil { | ||
panic("build ID not found") | ||
} | ||
buildID = id | ||
} | ||
|
||
var ( | ||
buildIDPrefix = []byte("\xff Go build ID: \"") | ||
buildIDEnd = []byte("\"\n \xff") | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package types | ||
|
||
import ( | ||
"bytes" | ||
"debug/elf" | ||
"os" | ||
) | ||
|
||
func init() { | ||
f, err := elf.Open(os.Args[0]) | ||
if err != nil { | ||
panic("cannot read elf binary: " + err.Error()) | ||
} | ||
defer f.Close() | ||
|
||
initELFFunctionTables(f) | ||
initELFBuildID(f) | ||
} | ||
|
||
func initELFFunctionTables(f *elf.File) { | ||
pclntab := f.Section(".gopclntab") | ||
pclntabData, err := readSection(pclntab, pclntab.Size) | ||
if err != nil { | ||
panic("cannot read pclntab: " + err.Error()) | ||
} | ||
symtab := f.Section(".gosymtab") | ||
symtabData, err := readSection(symtab, symtab.Size) | ||
if err != nil { | ||
panic("cannot read symtab: " + err.Error()) | ||
} | ||
initFunctionTables(pclntabData, symtabData) | ||
} | ||
|
||
func initELFBuildID(f *elf.File) { | ||
noteSection := f.Section(".note.go.buildid") | ||
note, err := readSection(noteSection, noteSection.Size) | ||
if err != nil { | ||
panic("cannot read build ID: " + err.Error()) | ||
} | ||
|
||
// See https://github.com/golang/go/blob/3803c858/src/cmd/internal/buildid/note.go#L135C3-L135C3 | ||
nameSize := f.ByteOrder.Uint32(note) | ||
valSize := f.ByteOrder.Uint32(note[4:]) | ||
tag := f.ByteOrder.Uint32(note[8:]) | ||
nname := note[12:16] | ||
if nameSize == 4 && 16+valSize <= uint32(len(note)) && tag == buildIDTag && bytes.Equal(nname, buildIDNote) { | ||
buildID = string(note[16 : 16+valSize]) | ||
} else { | ||
panic("build ID not found") | ||
} | ||
} | ||
|
||
var ( | ||
buildIDNote = []byte("Go\x00\x00") | ||
buildIDTag = uint32(4) | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters