capnproto migration from protobuf (#4)

This commit is contained in:
Alok Kumar Mishra authored and GitHub committed 2026-03-29 19:37:58 +08:00
1 parent a9e2c76254
commit 701b75f689
26 files changed
+2183 -1548

No files matched your search

+13
View File
@@ -110,8 +110,21 @@ jobs:
with:
go-version: ^1.24
- name: Install Cap'n Proto compiler
run: |
sudo apt-get update
sudo apt-get install -y capnproto
- name: Install host Cap'n Proto Go plugin
# capnpc-go must run on the GitHub runner, not the matrix target.
run: env -u GOOS -u GOARCH -u GOARM -u GOMIPS -u CGO_ENABLED go install capnproto.org/go/capnp/v3/[email protected]
- name: Get project dependencies
run: go mod download
- name: Generate host Cap'n Proto bindings
# go generate builds and executes generate.go, so it must use host Go env.
run: env -u GOOS -u GOARCH -u GOARM -u GOMIPS -u CGO_ENABLED go generate ./servicecapnp
- name: Build rz-silhouette-server
run: go build -o rz-silhouette-server-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.goarm }}
+6 -1
View File
@@ -1 +1,6 @@
rz-silhouette-server
__pycache__/
.tmp/
config.local.yaml
state/
artifacts/
servicecapnp/service.capnp.go
+24 -11
View File
@@ -1,27 +1,40 @@
# Rizin Silhouette Server
This server is used to provide signatures using the [rz-silhouette](https://github.com/rizinorg/rz-silhouette) plugin for rizin.
This server provides signatures, symbols, and hints to the [rz-silhouette](https://github.com/rizinorg/rz-silhouette) plugin for Rizin.
## Generating signatures
## Usage
1. Build the https://github.com/rizinorg/rz-silhouette-server and install https://github.com/rizinorg/rz-silhouette
2. Run `rz-silhouette-server -c config.yaml`
3. Execute `rizin -Qc 'aa ; sil share' /path/to/the/binary.exe`
Ensure that the `rizin` plugin `rz-silhouette` is configured to point towards the server and that the user can share the info with the server.
Ensure that the `rizin` plugin `rz-silhouette` points to the server and that the configured PSK is allowed to upload.
## Info the server provides
## Compilation
The server will provide 2 types of info
Install Go, `capnp`, and `capnpc-go`, then build from a clean checkout:
1. Hints based on the match on the binary section
2. Symbols based on their signatures
```sh
go generate ./servicecapnp
go build
```
## Server behaviour
For local installation, place the resulting `rz-silhouette-server` binary somewhere on your `PATH` or run it from the build directory.
The server stores the info into databases.
Some clients (based on the configuration) can be allowed to upload new signatures and hints.
For cross-compilation, generate the bindings on the build host first and then run `go build` with the target `GOOS` and `GOARCH`. Do not run `go generate` under the target environment, because the generator itself must execute on the host machine.
## Protocol
The server provides a Cap'n Proto exact-match resolve/share protocol.
## Storage
The server stores exact-match data in BoltDB. Some clients, based on the configuration, can upload new signatures and hints.
## Security
Cap'n Proto does not encrypt traffic. If you expose the raw TCP port, the PSK is still sent in clear text. Set `capnp_require_tls: true` and use the TLS listener for protected deployments.
## Documentation
Documentation is available [here](https://github.com/rizinorg/rz-silhouette/tree/main/docs)
Documentation is available [here](https://github.com/rizinorg/rz-silhouette/tree/main/docs)
+51 -18
View File
@@ -5,10 +5,11 @@ package main
import (
"crypto/md5"
"crypto/sha256"
"crypto/tls"
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"github.com/rs/zerolog"
@@ -18,32 +19,32 @@ import (
)
const (
MIN_VERSION = uint32(1)
GENERIC_DB = "any"
GENERIC_DB = "any"
)
type Resource struct {
Arch string `yaml:"arch"`
Bits int `yaml:"bits`
Bits int `yaml:"bits"`
Files []string `yaml:"files"`
}
type Config struct {
MaxQueue int `yaml:"max_queue"`
MaxPacket int64 `yaml:"max_packet"`
LogLevel string `yaml:"log_level"`
RawBind string `yaml:"raw-bind"`
TlsBind string `yaml:"tls-bind"`
TlsKey string `yaml:"tls-key"`
TlsCert string `yaml:"tls-cert"`
Message string `yaml:"message"`
UploadDir string `yaml:"upload_dir"`
Resources []Resource `yaml:"resources"`
Authorized map[string]bool `yaml:"authorized"`
MaxQueue int `yaml:"max_queue"`
MaxPacket int64 `yaml:"max_packet"`
LogLevel string `yaml:"log_level"`
RawBind string `yaml:"raw-bind"`
TlsBind string `yaml:"tls-bind"`
TlsKey string `yaml:"tls-key"`
TlsCert string `yaml:"tls-cert"`
Message string `yaml:"message"`
UploadDir string `yaml:"upload_dir"`
CapnpRequireTLS bool `yaml:"capnp_require_tls"`
Resources []Resource `yaml:"resources"`
Authorized map[string]bool `yaml:"authorized"`
}
func readConfig(filename string, config *Config) error {
body, err := ioutil.ReadFile(filename)
body, err := os.ReadFile(filename)
if err != nil {
return err
}
@@ -81,6 +82,36 @@ func readConfig(filename string, config *Config) error {
return nil
}
func sharedDBFilename(dir, key string) string {
sum := sha256.Sum256([]byte(key))
return filepath.Join(dir, fmt.Sprintf("%x.db", sum))
}
func legacySharedDBFilename(dir, key string) string {
sum := md5.Sum([]byte(key))
return filepath.Join(dir, fmt.Sprintf("%x.db", sum))
}
func resolveSharedDBFilename(dir, key string) (string, error) {
current := sharedDBFilename(dir, key)
if exists(current) {
return current, nil
}
legacy := legacySharedDBFilename(dir, key)
if !exists(legacy) {
return current, nil
}
if err := os.Rename(legacy, current); err == nil {
log.Warn().Msgf("migrated legacy shared db %s -> %s", legacy, current)
return current, nil
}
log.Warn().Msgf("using legacy shared db path %s because migration to %s failed", legacy, current)
return legacy, nil
}
func (c *Config) GetAuthorized() map[string]bool {
if c.UploadDir != "" {
return c.Authorized
@@ -176,8 +207,10 @@ func (c *Config) LoadResources() (map[string][]*bbolt.DB, map[string]*bbolt.DB)
if !canUpload {
continue
}
dbFile := fmt.Sprintf("%x.db", md5.Sum([]byte(key)))
dbFile = filepath.Join(c.UploadDir, dbFile)
dbFile, err := resolveSharedDBFilename(c.UploadDir, key)
if err != nil {
log.Fatal().Err(err).Send()
}
db, err := OpenDatabase(dbFile)
if err != nil {
+65
View File
@@ -0,0 +1,65 @@
package main
import (
"crypto/md5"
"crypto/sha256"
"fmt"
"os"
"path/filepath"
"testing"
)
func TestSharedDBFilenameUsesSHA256(t *testing.T) {
got := sharedDBFilename("/tmp/share", "demo-psk")
want := filepath.Join("/tmp/share", fmt.Sprintf("%x.db", sha256.Sum256([]byte("demo-psk"))))
if got != want {
t.Fatalf("sharedDBFilename() = %q, want %q", got, want)
}
}
func TestLegacySharedDBFilenameUsesMD5(t *testing.T) {
got := legacySharedDBFilename("/tmp/share", "demo-psk")
want := filepath.Join("/tmp/share", fmt.Sprintf("%x.db", md5.Sum([]byte("demo-psk"))))
if got != want {
t.Fatalf("legacySharedDBFilename() = %q, want %q", got, want)
}
}
func TestLoadResourcesMigratesLegacySharedDBFilename(t *testing.T) {
dir := t.TempDir()
psk := "demo-psk"
legacyPath := legacySharedDBFilename(dir, psk)
db, err := OpenDatabase(legacyPath)
if err != nil {
t.Fatal(err)
}
db.Close()
cfg := &Config{
MaxQueue: 1024,
MaxPacket: 1024 * 1024,
LogLevel: "warn",
UploadDir: dir,
Authorized: map[string]bool{psk: true},
}
_, shared := cfg.LoadResources()
sharedDB := shared[psk]
if sharedDB == nil {
t.Fatal("expected shared db to be opened")
}
t.Cleanup(func() {
sharedDB.Close()
})
currentPath := sharedDBFilename(dir, psk)
if !exists(currentPath) {
t.Fatalf("expected migrated db at %q", currentPath)
}
if exists(legacyPath) {
t.Fatalf("expected legacy db %q to be migrated away", legacyPath)
}
if _, err := os.Stat(currentPath); err != nil {
t.Fatalf("expected migrated db to be readable: %v", err)
}
}
+91 -14
View File
@@ -6,6 +6,7 @@ package main
import (
"encoding/json"
"fmt"
"strings"
"time"
"go.etcd.io/bbolt"
@@ -19,17 +20,21 @@ const (
var (
DB_BUCKET_SYM = []byte("Symbols")
DB_BUCKET_SEC = []byte("Sections")
DB_BUCKET_LOC = []byte("Locations")
)
type MetaHints struct {
Author string
Name string
Hints []*Hint
Author string `json:"author"`
Name string `json:"name"`
BinaryID string `json:"binary_id,omitempty"`
Hints []*Hint `json:"hints"`
}
type MetaSymbol struct {
Author string
Symbol *Symbol
Author string `json:"author"`
BinaryID string `json:"binary_id,omitempty"`
Symbol *Symbol `json:"symbol"`
Size uint32 `json:"size,omitempty"`
}
func SignatureToKey(s *Signature) []byte {
@@ -42,6 +47,11 @@ func SignatureToKey(s *Signature) []byte {
return append([]byte(prefix), s.Digest...)
}
func FunctionLocationToKey(binaryID, sectionName string, sectionPaddr, sectionOffset uint64) []byte {
sectionName = strings.TrimSpace(sectionName)
return []byte(fmt.Sprintf("%s|%s|%016x|%016x", binaryID, sectionName, sectionPaddr, sectionOffset))
}
func SectionHashToKey(sh *SectionHash, binType, binOs string) []byte {
btype := sanitizeWord(binType, "any")
bos := sanitizeWord(binOs, "any")
@@ -73,6 +83,14 @@ func getValue(db *bbolt.DB, name, key []byte) ([]byte, error) {
}
func DbGetHints(db *bbolt.DB, key []byte) ([]*Hint, error) {
meta, err := DbGetHintsMeta(db, key)
if meta == nil || err != nil {
return nil, err
}
return meta.Hints, nil
}
func DbGetHintsMeta(db *bbolt.DB, key []byte) (*MetaHints, error) {
value, err := getValue(db, DB_BUCKET_SEC, key)
if value == nil {
return nil, err
@@ -80,7 +98,7 @@ func DbGetHints(db *bbolt.DB, key []byte) ([]*Hint, error) {
ms := MetaHints{}
err = json.Unmarshal(value, &ms)
return ms.Hints, err
return &ms, err
}
func DbHasHints(db *bbolt.DB, key []byte) (bool, error) {
@@ -88,11 +106,12 @@ func DbHasHints(db *bbolt.DB, key []byte) (bool, error) {
return value != nil, err
}
func DbSetHints(db *bbolt.DB, key []byte, author, name string, hints []*Hint) error {
func DbSetHintsMeta(db *bbolt.DB, key []byte, author, name, binaryID string, hints []*Hint) error {
ms := MetaHints{
Author: author,
Name: name,
Hints: hints,
Author: author,
Name: name,
BinaryID: binaryID,
Hints: hints,
}
value, err := json.Marshal(&ms)
@@ -103,7 +122,19 @@ func DbSetHints(db *bbolt.DB, key []byte, author, name string, hints []*Hint) er
return setValue(db, DB_BUCKET_SEC, key, value)
}
func DbSetHints(db *bbolt.DB, key []byte, author, name string, hints []*Hint) error {
return DbSetHintsMeta(db, key, author, name, "", hints)
}
func DbGetSymbol(db *bbolt.DB, key []byte) (*Symbol, error) {
meta, err := DbGetSymbolMeta(db, key)
if meta == nil || err != nil {
return nil, err
}
return meta.Symbol, nil
}
func DbGetSymbolMeta(db *bbolt.DB, key []byte) (*MetaSymbol, error) {
value, err := getValue(db, DB_BUCKET_SYM, key)
if value == nil {
return nil, err
@@ -111,7 +142,18 @@ func DbGetSymbol(db *bbolt.DB, key []byte) (*Symbol, error) {
ms := MetaSymbol{}
err = json.Unmarshal(value, &ms)
return ms.Symbol, err
return &ms, err
}
func DbGetLocationSymbolMeta(db *bbolt.DB, key []byte) (*MetaSymbol, error) {
value, err := getValue(db, DB_BUCKET_LOC, key)
if value == nil {
return nil, err
}
ms := MetaSymbol{}
err = json.Unmarshal(value, &ms)
return &ms, err
}
func DbHasSymbol(db *bbolt.DB, key []byte) (bool, error) {
@@ -119,14 +161,21 @@ func DbHasSymbol(db *bbolt.DB, key []byte) (bool, error) {
return value != nil, err
}
func DbSetSymbol(db *bbolt.DB, key []byte, author string, sym *Symbol) error {
func DbHasLocationSymbol(db *bbolt.DB, key []byte) (bool, error) {
value, err := getValue(db, DB_BUCKET_LOC, key)
return value != nil, err
}
func DbSetSymbolMeta(db *bbolt.DB, key []byte, author, binaryID string, sym *Symbol, size uint32) error {
if sym == nil {
return nil
}
ms := MetaSymbol{
Author: author,
Symbol: sym,
Author: author,
BinaryID: binaryID,
Symbol: sym,
Size: size,
}
value, err := json.Marshal(&ms)
@@ -137,6 +186,30 @@ func DbSetSymbol(db *bbolt.DB, key []byte, author string, sym *Symbol) error {
return setValue(db, DB_BUCKET_SYM, key, value)
}
func DbSetLocationSymbolMeta(db *bbolt.DB, key []byte, author, binaryID string, sym *Symbol, size uint32) error {
if sym == nil {
return nil
}
ms := MetaSymbol{
Author: author,
BinaryID: binaryID,
Symbol: sym,
Size: size,
}
value, err := json.Marshal(&ms)
if err != nil {
return err
}
return setValue(db, DB_BUCKET_LOC, key, value)
}
func DbSetSymbol(db *bbolt.DB, key []byte, author string, sym *Symbol) error {
return DbSetSymbolMeta(db, key, author, "", sym, 0)
}
func initDatabase(tx *bbolt.Tx) error {
_, err := tx.CreateBucketIfNotExists(DB_BUCKET_SYM)
if err != nil {
@@ -146,6 +219,10 @@ func initDatabase(tx *bbolt.Tx) error {
if err != nil {
return err
}
_, err = tx.CreateBucketIfNotExists(DB_BUCKET_LOC)
if err != nil {
return err
}
return nil
}
+53
View File
@@ -0,0 +1,53 @@
package main
import (
"path/filepath"
"testing"
"time"
"go.etcd.io/bbolt"
)
func TestOpenDatabaseUpgradesLegacyBuckets(t *testing.T) {
path := filepath.Join(t.TempDir(), "legacy.db")
legacyDB, err := bbolt.Open(path, DB_PERM, &bbolt.Options{Timeout: 2 * time.Second})
if err != nil {
t.Fatal(err)
}
err = legacyDB.Update(func(tx *bbolt.Tx) error {
if _, err := tx.CreateBucketIfNotExists(DB_BUCKET_SYM); err != nil {
return err
}
if _, err := tx.CreateBucketIfNotExists(DB_BUCKET_SEC); err != nil {
return err
}
return nil
})
legacyDB.Close()
if err != nil {
t.Fatal(err)
}
db, err := OpenDatabase(path)
if err != nil {
t.Fatal(err)
}
defer db.Close()
err = db.View(func(tx *bbolt.Tx) error {
if tx.Bucket(DB_BUCKET_SYM) == nil {
t.Fatal("Symbols bucket missing")
}
if tx.Bucket(DB_BUCKET_SEC) == nil {
t.Fatal("Sections bucket missing")
}
if tx.Bucket(DB_BUCKET_LOC) == nil {
t.Fatal("Locations bucket missing")
}
return nil
})
if err != nil {
t.Fatal(err)
}
}
+4
View File
@@ -37,6 +37,10 @@ resources:
# path to the upload database for those clients that are authorized.
upload_dir: /path/to/shared
# Cap'n Proto only changes serialization. Keep this enabled for real deployments
# unless the raw TCP listener is strictly local and trusted.
capnp_require_tls: true
# Map of 'psk: bool' of authorized clients which can and cannot upload
authorized:
39b1212a-944e-4b4f-83a7-33a3be33e314: true
+3 -1
View File
@@ -3,14 +3,16 @@ module rz-silhouette-server
go 1.20
require (
capnproto.org/go/capnp/v3 v3.1.0-alpha.2
github.com/rs/zerolog v1.29.0
go.etcd.io/bbolt v1.3.7
google.golang.org/protobuf v1.30.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/colega/zeropool v0.0.0-20230505084239-6fb4a4f75381 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.6.0 // indirect
)
+10 -9
View File
@@ -1,30 +1,31 @@
capnproto.org/go/capnp/v3 v3.1.0-alpha.2 h1:93ISpqHf2/3WQlfrBP0tT8Dg/RQc3uq6DV36vN0ePWk=
capnproto.org/go/capnp/v3 v3.1.0-alpha.2/go.mod h1:2vT5D2dtG8sJGEoEKU17e+j7shdaYp1Myl8X03B3hmc=
github.com/colega/zeropool v0.0.0-20230505084239-6fb4a4f75381 h1:d5EKgQfRQvO97jnISfR89AiCCCJMwMFoSxUiU0OGCRU=
github.com/colega/zeropool v0.0.0-20230505084239-6fb4a4f75381/go.mod h1:OU76gHeRo8xrzGJU3F3I1CqX1ekM8dfJw0+wPeMwnp0=
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w=
github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/tinylib/msgp v1.1.9 h1:SHf3yoO2sGA0veCJeCBYLHuttAVFHGm2RHgNodW7wQU=
github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk=
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+52 -170
View File
@@ -1,243 +1,125 @@
// SPDX-FileCopyrightText: 2023 RizinOrg <[email protected]>
// SPDX-FileCopyrightText: 2023 deroad <[email protected]>
// SPDX-License-Identifier: LGPL-3.0-only
package main
import (
"bytes"
"crypto/tls"
"encoding/binary"
"io"
"net"
"time"
"github.com/rs/zerolog/log"
"google.golang.org/protobuf/proto"
)
const (
HEADER_SIZE = int64(4)
HEADER_SIZE = int64(4)
PROTOCOL_MAGIC = "SILC"
)
type requestLogInfo struct {
psk string
route string
version uint32
}
func readFromConn(conn net.Conn, expected int64) ([]byte, bool) {
conn.SetReadDeadline(time.Now().Add(TIMEOUT_CONN))
var buffer bytes.Buffer
writer := io.Writer(&buffer)
if expected < 0 || expected > MAX_BODY_LEN {
log.Info().Stringer("ip", conn.RemoteAddr()).Msgf("Invalid requested read size %d", expected)
return nil, false
}
_, err := io.CopyN(writer, conn, expected)
buffer := make([]byte, expected)
_, err := io.ReadFull(conn, buffer)
if err == io.EOF {
return nil, false
} else if err != nil {
log.Info().Stringer("ip", conn.RemoteAddr()).Err(err).Send()
return nil, false
}
b := buffer.Bytes()
if len(b) != int(expected) {
log.Info().Stringer("ip", conn.RemoteAddr()).Msgf("Expected %d but received only %d", expected, len(b))
return nil, false
}
return b, true
return buffer, true
}
func readIncomingRequest(conn net.Conn) *Request {
func readIncomingPacket(conn net.Conn) ([]byte, bool) {
header, ok := readFromConn(conn, HEADER_SIZE)
if !ok {
log.Info().Stringer("ip", conn.RemoteAddr()).Msg("Failed to receive packet size.")
return nil
return nil, false
}
packetSize := int64(binary.BigEndian.Uint32(header))
if packetSize > MAX_BODY_LEN {
log.Info().Stringer("ip", conn.RemoteAddr()).Msgf("Expected small body size (%d) but received %d", MAX_BODY_LEN, packetSize)
return nil
return nil, false
}
packet, ok := readFromConn(conn, packetSize)
if !ok {
log.Info().Stringer("ip", conn.RemoteAddr()).Msg("Failed to receive packet")
return nil
return nil, false
}
req := &Request{}
if err := proto.Unmarshal(packet, req); err != nil {
log.Info().Stringer("ip", conn.RemoteAddr()).Err(err).Msg("Failed to decode request")
return nil
if len(packet) < len(PROTOCOL_MAGIC) || string(packet[:len(PROTOCOL_MAGIC)]) != PROTOCOL_MAGIC {
log.Info().Stringer("ip", conn.RemoteAddr()).Msg("Failed to receive capnp packet magic")
return nil, false
}
return req
return packet[len(PROTOCOL_MAGIC):], true
}
func writeToConn(conn net.Conn, buffer []byte) bool {
var b = bytes.NewBuffer(buffer)
reader := io.Reader(b)
written, err := io.Copy(conn, reader)
if err == io.EOF {
return false
} else if err != nil {
log.Info().Stringer("ip", conn.RemoteAddr()).Err(err).Send()
return false
} else if int(written) != len(buffer) {
log.Info().Stringer("ip", conn.RemoteAddr()).Msg("Failed to send all the bytes")
return false
offset := 0
for offset < len(buffer) {
written, err := conn.Write(buffer[offset:])
if err == io.EOF {
return false
} else if err != nil {
log.Info().Stringer("ip", conn.RemoteAddr()).Err(err).Send()
return false
}
offset += written
}
return true
}
func writeOutgoingResponse(conn net.Conn, body proto.Message, status Status) bool {
var message []byte = nil
var err error = nil
if body != nil {
message, err = proto.Marshal(body)
if err != nil {
log.Info().Stringer("ip", conn.RemoteAddr()).Err(err).Msg("Failed to encode body")
return false
}
}
resp := Response{
Status: status,
Message: message,
}
packet, err := proto.Marshal(&resp)
if err != nil {
log.Info().Stringer("ip", conn.RemoteAddr()).Err(err).Msg("Failed to encode response")
return false
}
func writePacket(conn net.Conn, body []byte) bool {
body = append([]byte(PROTOCOL_MAGIC), body...)
header := make([]byte, 4)
binary.BigEndian.PutUint32(header[:], uint32(len(packet)))
if writeToConn(conn, header) && writeToConn(conn, packet) {
binary.BigEndian.PutUint32(header[:], uint32(len(body)))
if writeToConn(conn, header) && writeToConn(conn, body) {
return true
}
log.Info().Stringer("ip", conn.RemoteAddr()).Msg("Failed to send response packet")
return false
}
func handlePingRequest(server *Server, conn net.Conn, request *Request) {
message := Message{
Text: server.motd,
}
writeOutgoingResponse(conn, &message, Status_MESSAGE)
func isTLSConn(conn net.Conn) bool {
_, ok := conn.(*tls.Conn)
return ok
}
func handleBinaryRequest(server *Server, conn net.Conn, request *Request) {
var binary = Binary{}
if err := proto.Unmarshal(request.Message, &binary); err != nil {
log.Info().
Stringer("ip", conn.RemoteAddr()).
Str("psk", request.Psk).
Stringer("route", request.Route).
Err(err).
Send()
return
}
var matches = MatchHints{}
for _, section := range binary.Sections {
hints := server.GetHints(request.Psk, &binary, section)
if hints == nil {
continue
}
for _, hint := range hints {
hint.Offset += section.Paddr
}
if matches.Hints == nil {
matches.Hints = hints
} else {
matches.Hints = append(matches.Hints, hints...)
}
}
writeOutgoingResponse(conn, &matches, Status_HINTS)
}
func handleSignatureRequest(server *Server, conn net.Conn, request *Request) {
var signature = Signature{}
if err := proto.Unmarshal(request.Message, &signature); err != nil {
log.Info().Stringer("ip", conn.RemoteAddr()).
Str("psk", request.Psk).
Stringer("route", request.Route).
Err(err).
Send()
return
}
message := server.GetSymbol(request.Psk, &signature)
writeOutgoingResponse(conn, message, Status_SYMBOL)
}
func handleShareBinRequest(server *Server, conn net.Conn, request *Request) {
if !server.CanShare(request) {
writeOutgoingResponse(conn, nil, Status_CLIENT_NOT_AUTHORIZED)
return
}
var bin = ShareBin{}
if err := proto.Unmarshal(request.Message, &bin); err != nil {
log.Info().Stringer("ip", conn.RemoteAddr()).
Str("psk", request.Psk).
Stringer("route", request.Route).
Err(err).
Send()
return
}
if bin.Sections != nil {
for _, section := range bin.Sections {
server.SetHints(request.Psk, &bin, section)
}
}
if bin.Symbols != nil {
for _, share := range bin.Symbols {
server.SetSymbol(request.Psk, share.Signature, share.Symbol)
}
}
writeOutgoingResponse(conn, nil, Status_SHARE_WAS_SUCCESSFUL)
}
func logRequest(conn net.Conn, request *Request, start time.Time) {
func logRequest(conn net.Conn, info requestLogInfo, start time.Time) {
end := time.Now()
log.Warn().
Stringer("ip", conn.RemoteAddr()).
Str("psk", request.Psk).
Stringer("route", request.Route).
Str("codec", "capnp").
Str("psk", info.psk).
Str("route", info.route).
Uint32("version", info.version).
TimeDiff("duration", end, start).
Send()
}
func handleConnection(server *Server, conn net.Conn) {
var start = time.Now()
var request *Request = nil
start := time.Now()
defer conn.Close()
request = readIncomingRequest(conn)
if request == nil {
packet, ok := readIncomingPacket(conn)
if !ok {
return
}
if !server.IsAuthorized(request) {
writeOutgoingResponse(conn, nil, Status_CLIENT_BAD_PRE_SHARED_KEY)
logRequest(conn, request, start)
return
info, handled := handleCapnpConnection(server, conn, packet)
if handled {
logRequest(conn, info, start)
}
switch request.Route {
case Route_PING:
handlePingRequest(server, conn, request)
case Route_BINARY:
handleBinaryRequest(server, conn, request)
case Route_SIGNATURE:
handleSignatureRequest(server, conn, request)
case Route_SHARE_BIN:
handleShareBinRequest(server, conn, request)
default:
}
logRequest(conn, request, start)
}
+364
View File
@@ -0,0 +1,364 @@
// SPDX-FileCopyrightText: 2026 RizinOrg <[email protected]>
// SPDX-License-Identifier: LGPL-3.0-only
package main
import (
"fmt"
"net"
capnp "capnproto.org/go/capnp/v3"
"github.com/rs/zerolog/log"
"rz-silhouette-server/servicecapnp"
)
type capnpRequest struct {
Psk string
Version uint32
Route servicecapnp.SilRoute
Program ProgramBundle
}
func decodeCapnpRequest(packet []byte) (capnpRequest, error) {
msg, err := capnp.UnmarshalPacked(packet)
if err != nil {
return capnpRequest{}, err
}
root, err := servicecapnp.ReadRootSilRequest(msg)
if err != nil {
return capnpRequest{}, err
}
psk, err := root.Psk()
if err != nil {
return capnpRequest{}, err
}
req := capnpRequest{
Psk: psk,
Version: root.Version(),
Route: root.Route(),
}
switch root.Which() {
case servicecapnp.SilRequest_Which_ping:
if req.Route != servicecapnp.SilRoute_ping {
return capnpRequest{}, fmt.Errorf("capnp route mismatch: %s/%s", req.Route, root.Which())
}
case servicecapnp.SilRequest_Which_resolveProgram:
if req.Route != servicecapnp.SilRoute_resolveProgram {
return capnpRequest{}, fmt.Errorf("capnp route mismatch: %s/%s", req.Route, root.Which())
}
resolveReq, err := root.ResolveProgram()
if err != nil {
return capnpRequest{}, err
}
req.Program, err = decodeProgramBundle(resolveReq.Program)
if err != nil {
return capnpRequest{}, err
}
case servicecapnp.SilRequest_Which_shareProgram:
if req.Route != servicecapnp.SilRoute_shareProgram {
return capnpRequest{}, fmt.Errorf("capnp route mismatch: %s/%s", req.Route, root.Which())
}
shareReq, err := root.ShareProgram()
if err != nil {
return capnpRequest{}, err
}
req.Program, err = decodeProgramBundle(shareReq.Program)
if err != nil {
return capnpRequest{}, err
}
default:
return capnpRequest{}, fmt.Errorf("unsupported capnp union %s", root.Which())
}
return req, nil
}
func decodeProgramBundle(get func() (servicecapnp.SilProgramBundle, error)) (ProgramBundle, error) {
program, err := get()
if err != nil {
return ProgramBundle{}, err
}
binaryType, err := program.BinaryType()
if err != nil {
return ProgramBundle{}, err
}
binaryOS, err := program.Os()
if err != nil {
return ProgramBundle{}, err
}
arch, err := program.Arch()
if err != nil {
return ProgramBundle{}, err
}
binaryID, err := program.BinaryId()
if err != nil {
return ProgramBundle{}, err
}
out := ProgramBundle{
BinaryType: binaryType,
OS: binaryOS,
Arch: arch,
Bits: program.Bits(),
BinaryID: binaryID,
}
sections, err := program.Sections()
if err != nil {
return ProgramBundle{}, err
}
for i := 0; i < sections.Len(); i++ {
section := sections.At(i)
digest, err := section.Digest()
if err != nil {
return ProgramBundle{}, err
}
name, err := section.Name()
if err != nil {
return ProgramBundle{}, err
}
out.Sections = append(out.Sections, SectionRecord{
Name: name,
Size: section.Size(),
Paddr: section.Paddr(),
Digest: append([]byte(nil), digest...),
})
}
functions, err := program.Functions()
if err != nil {
return ProgramBundle{}, err
}
for i := 0; i < functions.Len(); i++ {
function := functions.At(i)
digest, err := function.Digest()
if err != nil {
return ProgramBundle{}, err
}
arch, err := function.Arch()
if err != nil {
return ProgramBundle{}, err
}
sectionName, err := function.SectionName()
if err != nil {
return ProgramBundle{}, err
}
name, err := function.Name()
if err != nil {
return ProgramBundle{}, err
}
signature, err := function.Signature()
if err != nil {
return ProgramBundle{}, err
}
callconv, err := function.Callconv()
if err != nil {
return ProgramBundle{}, err
}
out.Functions = append(out.Functions, FunctionRecord{
Addr: function.Addr(),
Size: function.Size(),
Bits: function.Bits(),
Arch: arch,
Length: function.Length(),
Digest: append([]byte(nil), digest...),
SectionName: sectionName,
SectionPaddr: function.SectionPaddr(),
SectionOffset: function.SectionOffset(),
Name: name,
Signature: signature,
Callconv: callconv,
})
}
return normalizeProgramBundle(out), nil
}
func writeCapnpMessageResponse(conn net.Conn, status servicecapnp.SilStatus, text string) bool {
msg, seg := capnp.NewSingleSegmentMessage(nil)
root, err := servicecapnp.NewRootSilResponse(seg)
if err != nil {
log.Info().Stringer("ip", conn.RemoteAddr()).Err(err).Msg("Failed to allocate capnp response")
return false
}
root.SetStatus(status)
message, err := root.NewMessage_()
if err != nil {
log.Info().Stringer("ip", conn.RemoteAddr()).Err(err).Msg("Failed to allocate capnp message body")
return false
}
if err := message.SetText(text); err != nil {
log.Info().Stringer("ip", conn.RemoteAddr()).Err(err).Msg("Failed to set capnp response text")
return false
}
packet, err := msg.MarshalPacked()
if err != nil {
log.Info().Stringer("ip", conn.RemoteAddr()).Err(err).Msg("Failed to encode capnp response")
return false
}
return writePacket(conn, packet)
}
func writeCapnpServerInfo(conn net.Conn, tlsRequired bool) bool {
msg, seg := capnp.NewSingleSegmentMessage(nil)
root, err := servicecapnp.NewRootSilResponse(seg)
if err != nil {
return false
}
root.SetStatus(servicecapnp.SilStatus_serverInfo)
serverInfo, err := root.NewServerInfo()
if err != nil {
return false
}
codecs, err := serverInfo.NewSupportedCodecs(1)
if err != nil {
return false
}
codecs.Set(0, servicecapnp.SilCodec_capnp)
serverInfo.SetVersion(PROTOCOL_VERSION)
serverInfo.SetTlsRequired(tlsRequired)
packet, err := msg.MarshalPacked()
if err != nil {
return false
}
return writePacket(conn, packet)
}
func writeCapnpResolveResult(conn net.Conn, result ResolveProgramResult) bool {
msg, seg := capnp.NewSingleSegmentMessage(nil)
root, err := servicecapnp.NewRootSilResponse(seg)
if err != nil {
return false
}
root.SetStatus(servicecapnp.SilStatus_resolveResult)
resolveResult, err := root.NewResolveResult()
if err != nil {
return false
}
if err := populateCapnpResolveResult(resolveResult, result); err != nil {
log.Info().Stringer("ip", conn.RemoteAddr()).Err(err).Msg("Failed to encode resolve result")
return false
}
packet, err := msg.MarshalPacked()
if err != nil {
return false
}
return writePacket(conn, packet)
}
func populateCapnpResolveResult(dst servicecapnp.SilResolveResult, result ResolveProgramResult) error {
hints, err := dst.NewHints(int32(len(result.Hints)))
if err != nil {
return err
}
for i, hint := range result.Hints {
entry := hints.At(i)
entry.SetBits(hint.Bits)
entry.SetOffset(hint.Offset)
}
symbols, err := dst.NewSymbols(int32(len(result.Symbols)))
if err != nil {
return err
}
for i, symbol := range result.Symbols {
entry := symbols.At(i)
entry.SetAddr(symbol.Addr)
entry.SetExact(symbol.Exact)
entry.SetOffset(symbol.Offset)
entry.SetSize(symbol.Size)
if err := entry.SetMatchedBinaryId(symbol.MatchedBinaryID); err != nil {
return err
}
if err := entry.SetMatchedBy(symbol.MatchedBy); err != nil {
return err
}
sym, err := entry.NewSymbol()
if err != nil {
return err
}
if err := sym.SetName(symbol.Symbol.Name); err != nil {
return err
}
if err := sym.SetSignature(symbol.Symbol.Signature); err != nil {
return err
}
if err := sym.SetCallconv(symbol.Symbol.Callconv); err != nil {
return err
}
sym.SetBits(symbol.Symbol.Bits)
}
return nil
}
func writeCapnpShareResult(conn net.Conn, result ShareProgramResult) bool {
msg, seg := capnp.NewSingleSegmentMessage(nil)
root, err := servicecapnp.NewRootSilResponse(seg)
if err != nil {
return false
}
root.SetStatus(servicecapnp.SilStatus_shareResult)
shareResult, err := root.NewShareResult()
if err != nil {
return false
}
if err := shareResult.SetBinaryId(result.BinaryID); err != nil {
return false
}
packet, err := msg.MarshalPacked()
if err != nil {
return false
}
return writePacket(conn, packet)
}
func handleCapnpConnection(server *Server, conn net.Conn, packet []byte) (requestLogInfo, bool) {
request, err := decodeCapnpRequest(packet)
if err != nil {
log.Info().Stringer("ip", conn.RemoteAddr()).Err(err).Msg("Failed to decode capnp request")
return requestLogInfo{}, false
}
info := requestLogInfo{
psk: request.Psk,
route: request.Route.String(),
version: request.Version,
}
_, exists := server.auths[request.Psk]
if request.Psk == "" || !exists {
writeCapnpMessageResponse(conn, servicecapnp.SilStatus_clientBadPreSharedKey, "client pre-shared key was rejected")
return info, true
}
if request.Version != PROTOCOL_VERSION {
writeCapnpMessageResponse(conn, servicecapnp.SilStatus_versionMismatch, fmt.Sprintf("client/server capnp protocol mismatch: got %d want %d", request.Version, PROTOCOL_VERSION))
return info, true
}
if server.capnpRequireTLS && !isTLSConn(conn) {
writeCapnpMessageResponse(conn, servicecapnp.SilStatus_clientNotAuthorized, "capnp requires TLS")
return info, true
}
switch request.Route {
case servicecapnp.SilRoute_ping:
writeCapnpServerInfo(conn, server.capnpRequireTLS)
case servicecapnp.SilRoute_resolveProgram:
result := server.ResolveProgram(request.Psk, request.Program)
writeCapnpResolveResult(conn, result)
case servicecapnp.SilRoute_shareProgram:
if !server.auths[request.Psk] {
writeCapnpMessageResponse(conn, servicecapnp.SilStatus_clientNotAuthorized, "client is not allowed to share")
return info, true
}
result := server.ShareProgram(request.Psk, request.Program)
writeCapnpShareResult(conn, result)
default:
writeCapnpMessageResponse(conn, servicecapnp.SilStatus_internalError, "unsupported capnp route")
}
return info, true
}
+129
View File
@@ -0,0 +1,129 @@
package main
import (
"encoding/hex"
"os"
"path/filepath"
"strings"
"testing"
capnp "capnproto.org/go/capnp/v3"
"rz-silhouette-server/servicecapnp"
)
func buildCapnpPingPacket(packed bool) ([]byte, error) {
msg, seg := capnp.NewSingleSegmentMessage(nil)
root, err := servicecapnp.NewRootSilRequest(seg)
if err != nil {
return nil, err
}
if err := root.SetPsk("demo-psk"); err != nil {
return nil, err
}
root.SetVersion(PROTOCOL_VERSION)
root.SetRoute(servicecapnp.SilRoute_ping)
if _, err := root.NewPing(); err != nil {
return nil, err
}
if packed {
return msg.MarshalPacked()
}
return msg.Marshal()
}
func TestDecodeCapnpRequestPing(t *testing.T) {
packet, err := buildCapnpPingPacket(true)
if err != nil {
t.Fatal(err)
}
req, err := decodeCapnpRequest(packet)
if err != nil {
t.Fatal(err)
}
if req.Psk != "demo-psk" {
t.Fatalf("unexpected psk: %q", req.Psk)
}
if req.Version != PROTOCOL_VERSION {
t.Fatalf("unexpected version: %d", req.Version)
}
if req.Route != servicecapnp.SilRoute_ping {
t.Fatalf("unexpected route: %s", req.Route)
}
}
func TestDecodeCapnpRequestPingUnpacked(t *testing.T) {
packet, err := buildCapnpPingPacket(false)
if err != nil {
t.Fatal(err)
}
msg, err := capnp.Unmarshal(packet)
if err != nil {
t.Fatal(err)
}
root, err := servicecapnp.ReadRootSilRequest(msg)
if err != nil {
t.Fatal(err)
}
psk, err := root.Psk()
if err != nil {
t.Fatal(err)
}
if psk != "demo-psk" {
t.Fatalf("unexpected psk: %q", psk)
}
if root.Version() != PROTOCOL_VERSION {
t.Fatalf("unexpected version: %d", root.Version())
}
if root.Route() != servicecapnp.SilRoute_ping {
t.Fatalf("unexpected route: %s", root.Route())
}
}
func TestCapnpPingGoldenFixture(t *testing.T) {
packet, err := buildCapnpPingPacket(true)
if err != nil {
t.Fatal(err)
}
body, err := os.ReadFile(filepath.Join("testdata", "capnp_ping_request.hex"))
if err != nil {
t.Fatal(err)
}
got := hex.EncodeToString(packet)
want := strings.TrimSpace(string(body))
if got != want {
t.Fatalf("capnp ping fixture drifted\n got: %s\nwant: %s", got, want)
}
}
func BenchmarkPingCodecCapnpPacked(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
packet, err := buildCapnpPingPacket(true)
if err != nil {
b.Fatal(err)
}
if _, err := decodeCapnpRequest(packet); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkPingCodecCapnpUnpacked(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
packet, err := buildCapnpPingPacket(false)
if err != nil {
b.Fatal(err)
}
msg, err := capnp.Unmarshal(packet)
if err != nil {
b.Fatal(err)
}
if _, err := servicecapnp.ReadRootSilRequest(msg); err != nil {
b.Fatal(err)
}
}
}
+283
View File
@@ -0,0 +1,283 @@
// SPDX-FileCopyrightText: 2026 RizinOrg <[email protected]>
// SPDX-License-Identifier: LGPL-3.0-only
package main
import "fmt"
func functionSignature(bundle ProgramBundle, fn FunctionRecord) *Signature {
if len(fn.Digest) < 1 {
return nil
}
arch := fn.Arch
if arch == "" {
arch = bundle.Arch
}
length := fn.Length
if length < 1 {
length = fn.Size
}
bits := fn.Bits
if bits < 1 {
bits = bundle.Bits
}
return &Signature{
Arch: arch,
Bits: bits,
Length: length,
Digest: append([]byte(nil), fn.Digest...),
}
}
func functionSymbol(fn FunctionRecord, bundleBits uint32) *Symbol {
if fn.Name == "" {
return nil
}
bits := fn.Bits
if bits < 1 {
bits = bundleBits
}
return &Symbol{
Name: fn.Name,
Signature: fn.Signature,
Callconv: fn.Callconv,
Bits: bits,
}
}
func sectionLookupKey(name string, paddr uint64) string {
return fmt.Sprintf("%s|%016x", name, paddr)
}
func sectionOffsetKey(name string, paddr, offset uint64) string {
return fmt.Sprintf("%s|%016x|%016x", name, paddr, offset)
}
func hintedSymbolSize(hints []*Hint, currentOffset uint64, fallback uint32) uint32 {
const maxMaterialize = 256
best := uint64(0)
for _, hint := range hints {
if hint == nil || hint.Offset <= currentOffset {
continue
}
span := hint.Offset - currentOffset
if span == 0 {
continue
}
if best == 0 || span < best {
best = span
}
}
if fallback > 0 && (best == 0 || uint64(fallback) < best) {
best = uint64(fallback)
}
if best == 0 {
best = 16
}
if best > maxMaterialize {
best = maxMaterialize
}
return uint32(best)
}
func buildShareSections(bundle ProgramBundle) []*ShareSection {
sectionsByKey := make(map[string]SectionRecord, len(bundle.Sections))
sectionsByAddr := make(map[uint64]SectionRecord, len(bundle.Sections))
for _, section := range bundle.Sections {
sectionsByKey[sectionLookupKey(section.Name, section.Paddr)] = section
sectionsByAddr[section.Paddr] = section
}
shared := map[string]*ShareSection{}
for _, fn := range bundle.Functions {
section, ok := sectionsByKey[sectionLookupKey(fn.SectionName, fn.SectionPaddr)]
if !ok {
section, ok = sectionsByAddr[fn.SectionPaddr]
}
if !ok || len(section.Digest) < 1 {
continue
}
key := sectionLookupKey(section.Name, section.Paddr)
entry, ok := shared[key]
if !ok {
entry = &ShareSection{
Name: section.Name,
Section: &SectionHash{
Size: section.Size,
Paddr: section.Paddr,
Digest: append([]byte(nil), section.Digest...),
},
}
shared[key] = entry
}
bits := fn.Bits
if bits < 1 {
bits = bundle.Bits
}
entry.Hints = append(entry.Hints, &Hint{
Bits: bits,
Offset: fn.SectionOffset,
})
}
out := make([]*ShareSection, 0, len(shared))
for _, section := range shared {
out = append(out, section)
}
return out
}
func (s *Server) ResolveProgram(psk string, bundle ProgramBundle) ResolveProgramResult {
bundle = normalizeProgramBundle(bundle)
result := ResolveProgramResult{}
sectionMetas := make(map[string]*MetaHints, len(bundle.Sections))
for _, section := range bundle.Sections {
meta := s.GetHintsMeta(psk, bundle.BinaryType, bundle.OS, &SectionHash{
Size: section.Size,
Paddr: section.Paddr,
Digest: section.Digest,
}, nil)
if meta == nil {
continue
}
sectionMetas[sectionLookupKey(section.Name, section.Paddr)] = meta
for _, hint := range meta.Hints {
if hint == nil {
continue
}
result.Hints = append(result.Hints, HintMatch{
Bits: hint.Bits,
Offset: hint.Offset + section.Paddr,
})
}
}
queryOffsets := make(map[string]struct{}, len(bundle.Functions))
for _, fn := range bundle.Functions {
queryOffsets[sectionOffsetKey(fn.SectionName, fn.SectionPaddr, fn.SectionOffset)] = struct{}{}
sectionKey := sectionLookupKey(fn.SectionName, fn.SectionPaddr)
sectionMeta := sectionMetas[sectionKey]
if sectionMeta != nil && sectionMeta.BinaryID != "" {
meta := s.GetLocationSymbolMeta(psk, sectionMeta.BinaryID, fn)
if meta != nil && meta.Symbol != nil {
result.Symbols = append(result.Symbols, SymbolMatchRecord{
Addr: fn.Addr,
Symbol: normalizeSymbolRecord(symbolRecordFromProto(meta.Symbol)),
Exact: true,
MatchedBinaryID: meta.BinaryID,
MatchedBy: "exact_location",
Offset: fn.SectionPaddr + fn.SectionOffset,
Size: hintedSymbolSize(sectionMeta.Hints, fn.SectionOffset, meta.Size),
})
continue
}
}
sig := functionSignature(bundle, fn)
if sig == nil {
continue
}
meta := s.GetSymbolMeta(psk, sig, nil)
if meta != nil && meta.Symbol != nil {
result.Symbols = append(result.Symbols, SymbolMatchRecord{
Addr: fn.Addr,
Symbol: normalizeSymbolRecord(symbolRecordFromProto(meta.Symbol)),
Exact: true,
MatchedBinaryID: meta.BinaryID,
MatchedBy: "exact_signature",
})
}
}
for _, section := range bundle.Sections {
sectionKey := sectionLookupKey(section.Name, section.Paddr)
meta := sectionMetas[sectionKey]
if meta == nil || meta.BinaryID == "" {
continue
}
for _, hint := range meta.Hints {
if hint == nil {
continue
}
key := sectionOffsetKey(section.Name, section.Paddr, hint.Offset)
if _, exists := queryOffsets[key]; exists {
continue
}
locationMeta := DbGetLocationSymbolMetaSafe(s, psk, meta.BinaryID, section.Name, section.Paddr, hint.Offset)
if locationMeta == nil || locationMeta.Symbol == nil {
continue
}
result.Symbols = append(result.Symbols, SymbolMatchRecord{
Addr: 0,
Symbol: normalizeSymbolRecord(symbolRecordFromProto(locationMeta.Symbol)),
Exact: true,
MatchedBinaryID: meta.BinaryID,
MatchedBy: "exact_hint_location",
Offset: section.Paddr + hint.Offset,
Size: hintedSymbolSize(meta.Hints, hint.Offset, locationMeta.Size),
})
}
}
return result
}
func DbGetLocationSymbolMetaSafe(s *Server, psk, binaryID, sectionName string, sectionPaddr, sectionOffset uint64) *MetaSymbol {
key := FunctionLocationToKey(binaryID, sectionName, sectionPaddr, sectionOffset)
for _, db := range s.GetHintsDbs() {
meta, err := DbGetLocationSymbolMeta(db, key)
if err != nil {
continue
}
if meta != nil {
return meta
}
}
return nil
}
func (s *Server) ShareProgram(psk string, bundle ProgramBundle) ShareProgramResult {
bundle = normalizeProgramBundle(bundle)
shareBin := &ShareBin{
Type: bundle.BinaryType,
Os: bundle.OS,
Sections: buildShareSections(bundle),
}
for _, fn := range bundle.Functions {
sig := functionSignature(bundle, fn)
sym := functionSymbol(fn, bundle.Bits)
if sig != nil && sym != nil {
shareBin.Symbols = append(shareBin.Symbols, &ShareSymbol{
Symbol: sym,
Signature: sig,
})
}
}
if shareBin.Sections != nil {
for _, section := range shareBin.Sections {
s.SetHintsWithBinaryID(psk, shareBin, section, bundle.BinaryID)
}
}
if shareBin.Symbols != nil {
symbolIndex := 0
for _, fn := range bundle.Functions {
if functionSignature(bundle, fn) == nil || functionSymbol(fn, bundle.Bits) == nil {
continue
}
share := shareBin.Symbols[symbolIndex]
s.SetSymbolWithBinaryID(psk, share.Signature, share.Symbol, bundle.BinaryID)
s.SetLocationSymbolWithBinaryID(psk, fn, share.Symbol, bundle.BinaryID)
symbolIndex++
}
}
return ShareProgramResult{BinaryID: bundle.BinaryID}
}
+353
View File
@@ -0,0 +1,353 @@
package main
import (
"path/filepath"
"testing"
"go.etcd.io/bbolt"
)
func newTestDB(t *testing.T, name string) *bbolt.DB {
t.Helper()
db, err := OpenDatabase(filepath.Join(t.TempDir(), name))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
db.Close()
})
return db
}
func TestResolveProgramUsesExactSignatureAndHints(t *testing.T) {
db := newTestDB(t, "match.db")
section := &SectionHash{Size: 0x30, Paddr: 0x4000, Digest: []byte{0xaa, 0xbb}}
sectionKey := SectionHashToKey(section, "elf", "linux")
if err := DbSetHintsMeta(db, sectionKey, "psk", ".text", "bin-match", []*Hint{{Bits: 64, Offset: 0x20}}); err != nil {
t.Fatal(err)
}
sig := &Signature{Arch: "x86", Bits: 64, Length: 16, Digest: []byte{0x01, 0x02}}
if err := DbSetSymbolMeta(db, SignatureToKey(sig), "psk", "bin-match", &Symbol{Name: "sym.good", Signature: "int good()", Callconv: "sysv", Bits: 64}, 0); err != nil {
t.Fatal(err)
}
server := &Server{
auths: map[string]bool{"psk": true},
search: map[string][]*bbolt.DB{GENERIC_DB: {db}},
shared: map[string]*bbolt.DB{},
}
result := server.ResolveProgram("psk", ProgramBundle{
BinaryType: "elf",
OS: "linux",
Arch: "x86",
Bits: 64,
Sections: []SectionRecord{
{Name: ".text", Size: 0x30, Paddr: 0x4000, Digest: []byte{0xaa, 0xbb}},
},
Functions: []FunctionRecord{
{Addr: 0x4020, Bits: 64, Arch: "x86", Length: 16, Digest: []byte{0x01, 0x02}},
},
})
if len(result.Hints) != 1 || result.Hints[0].Offset != 0x4020 {
t.Fatalf("unexpected hints: %#v", result.Hints)
}
if len(result.Symbols) != 1 {
t.Fatalf("unexpected symbols: %#v", result.Symbols)
}
if !result.Symbols[0].Exact || result.Symbols[0].Symbol.Name != "sym.good" {
t.Fatalf("exact symbol mismatch: %#v", result.Symbols[0])
}
if result.Symbols[0].MatchedBy != "exact_signature" {
t.Fatalf("unexpected match type: %#v", result.Symbols[0])
}
}
func TestShareProgramStoresExactDataWithBinaryID(t *testing.T) {
shareDB := newTestDB(t, "share.db")
server := &Server{
auths: map[string]bool{"psk": true},
search: map[string][]*bbolt.DB{GENERIC_DB: {shareDB}},
shared: map[string]*bbolt.DB{"psk": shareDB},
}
bundle := ProgramBundle{
BinaryType: "elf",
OS: "linux",
Arch: "x86",
Bits: 64,
Sections: []SectionRecord{
{Name: ".text", Size: 0x40, Paddr: 0x1000, Digest: []byte{0xde, 0xad}},
},
Functions: []FunctionRecord{
{
Addr: 0x1010,
Size: 16,
Bits: 64,
Arch: "x86",
Length: 16,
Digest: []byte{0xca, 0xfe},
SectionName: ".text",
SectionPaddr: 0x1000,
SectionOffset: 0x10,
Name: "sym.main",
Signature: "int main()",
Callconv: "sysv",
},
},
}
result := server.ShareProgram("psk", bundle)
if result.BinaryID == "" {
t.Fatal("share result binary id is empty")
}
normalized := normalizeProgramBundle(bundle)
sectionMeta, err := DbGetHintsMeta(shareDB, SectionHashToKey(&SectionHash{
Size: normalized.Sections[0].Size,
Paddr: normalized.Sections[0].Paddr,
Digest: normalized.Sections[0].Digest,
}, normalized.BinaryType, normalized.OS))
if err != nil {
t.Fatal(err)
}
if sectionMeta == nil || sectionMeta.BinaryID != result.BinaryID || len(sectionMeta.Hints) != 1 || sectionMeta.Hints[0].Offset != 0x10 {
t.Fatalf("unexpected section meta: %#v", sectionMeta)
}
sig := functionSignature(normalized, normalized.Functions[0])
symbolMeta, err := DbGetSymbolMeta(shareDB, SignatureToKey(sig))
if err != nil {
t.Fatal(err)
}
if symbolMeta == nil || symbolMeta.BinaryID != result.BinaryID || symbolMeta.Symbol == nil || symbolMeta.Symbol.Name != "sym.main" {
t.Fatalf("unexpected symbol meta: %#v", symbolMeta)
}
locationMeta, err := DbGetLocationSymbolMeta(shareDB, FunctionLocationToKey(result.BinaryID, normalized.Functions[0].SectionName, normalized.Functions[0].SectionPaddr, normalized.Functions[0].SectionOffset))
if err != nil {
t.Fatal(err)
}
if locationMeta == nil || locationMeta.Symbol == nil || locationMeta.Symbol.Name != "sym.main" {
t.Fatalf("unexpected location symbol meta: %#v", locationMeta)
}
if locationMeta.Size != 16 {
t.Fatalf("unexpected location symbol size: %#v", locationMeta)
}
}
func TestShareProgramPreservesVASectionOffsets(t *testing.T) {
shareDB := newTestDB(t, "share-va.db")
server := &Server{
auths: map[string]bool{"psk": true},
search: map[string][]*bbolt.DB{GENERIC_DB: {shareDB}},
shared: map[string]*bbolt.DB{"psk": shareDB},
}
bundle := ProgramBundle{
BinaryType: "elf",
OS: "linux",
Arch: "x86",
Bits: 64,
Sections: []SectionRecord{
{Name: ".text", Size: 0x40, Paddr: 0x1000, Digest: []byte{0xde, 0xad}},
},
Functions: []FunctionRecord{
{
Addr: 0x401010,
Size: 16,
Bits: 64,
Arch: "x86",
Length: 16,
Digest: []byte{0xca, 0xfe},
SectionName: ".text",
SectionPaddr: 0x1000,
SectionOffset: 0x10,
Name: "sym.main",
},
},
}
server.ShareProgram("psk", bundle)
normalized := normalizeProgramBundle(bundle)
sectionMeta, err := DbGetHintsMeta(shareDB, SectionHashToKey(&SectionHash{
Size: normalized.Sections[0].Size,
Paddr: normalized.Sections[0].Paddr,
Digest: normalized.Sections[0].Digest,
}, normalized.BinaryType, normalized.OS))
if err != nil {
t.Fatal(err)
}
if sectionMeta == nil || len(sectionMeta.Hints) != 1 || sectionMeta.Hints[0].Offset != 0x10 {
t.Fatalf("unexpected section meta: %#v", sectionMeta)
}
}
func TestResolveProgramPrefersExactLocationOverAmbiguousSignature(t *testing.T) {
db := newTestDB(t, "location.db")
server := &Server{
auths: map[string]bool{"psk": true},
search: map[string][]*bbolt.DB{GENERIC_DB: {db}},
shared: map[string]*bbolt.DB{"psk": db},
}
bundle := normalizeProgramBundle(ProgramBundle{
BinaryType: "elf",
OS: "linux",
Arch: "x86",
Bits: 64,
Sections: []SectionRecord{
{Name: ".text", Size: 0x80, Paddr: 0x1000, Digest: []byte{0xaa, 0xbb}},
},
Functions: []FunctionRecord{
{
Addr: 0x1010,
Size: 16,
Bits: 64,
Arch: "x86",
Length: 16,
Digest: []byte{0xca, 0xfe},
SectionName: ".text",
SectionPaddr: 0x1000,
SectionOffset: 0x10,
Name: "sym.real_name",
},
},
})
share := server.ShareProgram("psk", bundle)
if share.BinaryID == "" {
t.Fatal("expected binary id")
}
sig := functionSignature(bundle, bundle.Functions[0])
if err := DbSetSymbolMeta(db, SignatureToKey(sig), "psk", share.BinaryID, &Symbol{Name: "sym.wrong_name"}, 0); err != nil {
t.Fatal(err)
}
result := server.ResolveProgram("psk", ProgramBundle{
BinaryType: "elf",
OS: "linux",
Arch: "x86",
Bits: 64,
Sections: []SectionRecord{
{Name: ".text", Size: 0x80, Paddr: 0x1000, Digest: []byte{0xaa, 0xbb}},
},
Functions: []FunctionRecord{
{
Addr: 0x401010,
Bits: 64,
Arch: "x86",
Length: 16,
Digest: []byte{0xca, 0xfe},
SectionName: ".text",
SectionPaddr: 0x1000,
SectionOffset: 0x10,
},
},
})
if len(result.Symbols) != 1 {
t.Fatalf("unexpected symbols: %#v", result.Symbols)
}
if got := result.Symbols[0].Symbol.Name; got != "sym.real_name" {
t.Fatalf("expected location symbol, got %q", got)
}
if got := result.Symbols[0].MatchedBy; got != "exact_location" {
t.Fatalf("expected exact_location match, got %q", got)
}
if got := result.Symbols[0].Offset; got != 0x1010 {
t.Fatalf("expected exact offset, got %#x", got)
}
if got := result.Symbols[0].Size; got != 16 {
t.Fatalf("expected exact size, got %d", got)
}
}
func TestResolveProgramReturnsExtraExactLocationSymbolsForHintedOffsets(t *testing.T) {
db := newTestDB(t, "hinted-location.db")
server := &Server{
auths: map[string]bool{"psk": true},
search: map[string][]*bbolt.DB{GENERIC_DB: {db}},
shared: map[string]*bbolt.DB{"psk": db},
}
bundle := normalizeProgramBundle(ProgramBundle{
BinaryType: "elf",
OS: "linux",
Arch: "x86",
Bits: 64,
Sections: []SectionRecord{
{Name: ".text", Size: 0x80, Paddr: 0x1000, Digest: []byte{0xaa, 0xbb}},
},
Functions: []FunctionRecord{
{
Addr: 0x1010,
Size: 16,
Bits: 64,
Arch: "x86",
Length: 16,
Digest: []byte{0xca, 0xfe},
SectionName: ".text",
SectionPaddr: 0x1000,
SectionOffset: 0x10,
Name: "sym.main",
},
{
Addr: 0x1020,
Size: 8,
Bits: 64,
Arch: "x86",
Length: 8,
Digest: []byte{0xca, 0xff},
SectionName: ".text",
SectionPaddr: 0x1000,
SectionOffset: 0x20,
Name: "sym.cold",
},
},
})
share := server.ShareProgram("psk", bundle)
if share.BinaryID == "" {
t.Fatal("expected binary id")
}
result := server.ResolveProgram("psk", ProgramBundle{
BinaryType: "elf",
OS: "linux",
Arch: "x86",
Bits: 64,
Sections: []SectionRecord{
{Name: ".text", Size: 0x80, Paddr: 0x1000, Digest: []byte{0xaa, 0xbb}},
},
Functions: []FunctionRecord{
{
Addr: 0x401010,
Bits: 64,
Arch: "x86",
Length: 16,
Digest: []byte{0xca, 0xfe},
SectionName: ".text",
SectionPaddr: 0x1000,
SectionOffset: 0x10,
},
},
})
if len(result.Symbols) != 2 {
t.Fatalf("unexpected symbols: %#v", result.Symbols)
}
if got := result.Symbols[1].Symbol.Name; got != "sym.cold" {
t.Fatalf("expected hinted exact location symbol, got %q", got)
}
if got := result.Symbols[1].MatchedBy; got != "exact_hint_location" {
t.Fatalf("expected exact_hint_location match, got %q", got)
}
if got := result.Symbols[1].Offset; got != 0x1020 {
t.Fatalf("expected hinted offset, got %#x", got)
}
if got := result.Symbols[1].Size; got != 8 {
t.Fatalf("expected hinted size, got %d", got)
}
}
+118 -40
View File
@@ -24,12 +24,13 @@ var (
)
type Server struct {
motd string
auths map[string]bool
search map[string][]*bbolt.DB
shared map[string]*bbolt.DB
mutex sync.Mutex
queue chan net.Conn
motd string
auths map[string]bool
search map[string][]*bbolt.DB
shared map[string]*bbolt.DB
mutex sync.Mutex
queue chan net.Conn
capnpRequireTLS bool
}
func maxThreads() int {
@@ -67,7 +68,18 @@ func (s *Server) GetShareDB(key string) *bbolt.DB {
return s.shared[key]
}
func (s *Server) GetSymbol(psk string, sig *Signature) *Symbol {
func binaryIDAllowed(binaryID string, candidates map[string]struct{}) bool {
if len(candidates) < 1 {
return true
}
if binaryID == "" {
return true
}
_, ok := candidates[binaryID]
return ok
}
func (s *Server) GetSymbolMeta(psk string, sig *Signature, candidates map[string]struct{}) *MetaSymbol {
if sig == nil || sig.Digest == nil {
return nil
}
@@ -76,18 +88,37 @@ func (s *Server) GetSymbol(psk string, sig *Signature) *Symbol {
key := SignatureToKey(sig)
for _, db := range dbs {
sym, err := DbGetSymbol(db, key)
meta, err := DbGetSymbolMeta(db, key)
if err != nil {
log.Error().Str("psk", psk).Err(err).Send()
continue
} else if sym != nil {
return sym
} else if meta != nil && binaryIDAllowed(meta.BinaryID, candidates) {
return meta
}
}
return nil
}
func (s *Server) SetSymbol(psk string, sig *Signature, sym *Symbol) {
func (s *Server) GetLocationSymbolMeta(psk, binaryID string, fn FunctionRecord) *MetaSymbol {
if binaryID == "" || fn.SectionName == "" {
return nil
}
key := FunctionLocationToKey(binaryID, fn.SectionName, fn.SectionPaddr, fn.SectionOffset)
for _, db := range s.GetHintsDbs() {
meta, err := DbGetLocationSymbolMeta(db, key)
if err != nil {
log.Error().Str("psk", psk).Err(err).Send()
continue
}
if meta != nil {
return meta
}
}
return nil
}
func (s *Server) SetSymbolWithBinaryID(psk string, sig *Signature, sym *Symbol, binaryID string) {
if sig == nil || sym == nil || sig.Digest == nil || len(sym.Name) < 1 {
return
}
@@ -125,31 +156,84 @@ func (s *Server) SetSymbol(psk string, sig *Signature, sym *Symbol) {
}
s.mutex.Lock()
err := DbSetSymbol(db, key, psk, sym)
err := DbSetSymbolMeta(db, key, psk, binaryID, sym, 0)
s.mutex.Unlock()
if err != nil {
log.Error().Str("psk", psk).Err(err).Send()
} else {
log.Warn().Str("psk", psk).Str("arch", sig.Arch).Uint32("bits", sig.Bits).Str("name", sym.Name).Send()
log.Warn().
Str("psk", psk).
Str("arch", sig.Arch).
Uint32("bits", sig.Bits).
Str("name", sym.Name).
Str("binary_id", binaryID).
Send()
}
}
func (s *Server) GetHints(psk string, bin *Binary, sec *SectionHash) []*Hint {
func (s *Server) SetLocationSymbolWithBinaryID(psk string, fn FunctionRecord, sym *Symbol, binaryID string) {
if binaryID == "" || sym == nil || len(sym.Name) < 1 || fn.SectionName == "" {
return
}
db := s.GetShareDB(psk)
if db == nil {
log.Error().Str("psk", psk).Msg("tried to upload location symbols but db was not found")
return
}
key := FunctionLocationToKey(binaryID, fn.SectionName, fn.SectionPaddr, fn.SectionOffset)
found, err := DbHasLocationSymbol(db, key)
if err != nil {
log.Error().Str("psk", psk).Err(err).Send()
return
}
if found {
return
}
sym.Name = sanitizeSymbol(sym.Name)
sym.Signature = strings.TrimSpace(sym.Signature)
sym.Callconv = sanitizeWord(sym.Callconv, "")
if sym.Bits > uint32(1024) {
sym.Bits = 0
}
if sym.Name == "" {
return
}
s.mutex.Lock()
err = DbSetLocationSymbolMeta(db, key, psk, binaryID, sym, fn.Size)
s.mutex.Unlock()
if err != nil {
log.Error().Str("psk", psk).Err(err).Send()
}
}
func (s *Server) SetSymbol(psk string, sig *Signature, sym *Symbol) {
s.SetSymbolWithBinaryID(psk, sig, sym, "")
}
func (s *Server) GetHintsMeta(psk string, binType, binOS string, sec *SectionHash, candidates map[string]struct{}) *MetaHints {
if sec == nil || sec.Digest == nil {
return nil
}
dbs := s.GetHintsDbs()
key := SectionHashToKey(sec, bin.Type, bin.Os)
key := SectionHashToKey(sec, binType, binOS)
for _, db := range dbs {
hints, err := DbGetHints(db, key)
meta, err := DbGetHintsMeta(db, key)
if err != nil {
log.Error().Str("psk", psk).Err(err).Send()
continue
} else if hints != nil {
return hints
} else if meta != nil && binaryIDAllowed(meta.BinaryID, candidates) {
return meta
}
}
return nil
}
func (s *Server) SetHints(psk string, bin *ShareBin, sec *ShareSection) {
func (s *Server) SetHintsWithBinaryID(psk string, bin *ShareBin, sec *ShareSection, binaryID string) {
if sec == nil || sec.Hints == nil || len(sec.Hints) < 1 {
return
}
@@ -173,29 +257,22 @@ func (s *Server) SetHints(psk string, bin *ShareBin, sec *ShareSection) {
}
s.mutex.Lock()
err := DbSetHints(db, key, psk, sec.Name, sec.Hints)
err := DbSetHintsMeta(db, key, psk, sec.Name, binaryID, sec.Hints)
s.mutex.Unlock()
if err != nil {
log.Error().Str("psk", psk).Err(err).Send()
} else {
log.Warn().Str("psk", psk).Str("section", sec.Name).Int("hints", len(sec.Hints)).Send()
log.Warn().
Str("psk", psk).
Str("section", sec.Name).
Int("hints", len(sec.Hints)).
Str("binary_id", binaryID).
Send()
}
}
func (s *Server) IsAuthorized(req *Request) bool {
if req.Psk == "" {
return false
}
_, exists := s.auths[req.Psk]
return exists
}
func (s *Server) CanShare(req *Request) bool {
if req.Psk == "" {
return false
}
canShare, exists := s.auths[req.Psk]
return exists && canShare
func (s *Server) SetHints(psk string, bin *ShareBin, sec *ShareSection) {
s.SetHintsWithBinaryID(psk, bin, sec, "")
}
func (s *Server) Worker() {
@@ -234,11 +311,12 @@ func NewServer(config *Config) *Server {
}
server := &Server{
motd: config.Message,
auths: auths,
search: search,
shared: shared,
queue: make(chan net.Conn, maxQueue),
motd: config.Message,
auths: auths,
search: search,
shared: shared,
queue: make(chan net.Conn, maxQueue),
capnpRequireTLS: config.CapnpRequireTLS,
}
nThreads := maxThreads()
-1179
View File
File diff suppressed because it is too large. Load diff
-104
View File
@@ -1,104 +0,0 @@
// SPDX-FileCopyrightText: 2023 RizinOrg <[email protected]>
// SPDX-FileCopyrightText: 2023 deroad <[email protected]>
// SPDX-License-Identifier: LGPL-3.0-only
// protoc --go_out=. --go_opt=paths=source_relative service.proto
syntax = "proto3";
option go_package = "./main";
/* generics */
enum Route {
PING = 0;
BINARY = 1;
SIGNATURE = 2;
SHARE_BIN = 3;
}
enum Status {
INTERNAL_ERROR = 0;
CLIENT_BAD_PRE_SHARED_KEY = 1;
CLIENT_NOT_AUTHORIZED = 2;
VERSION_MISMATCH = 3;
SHARE_WAS_SUCCESSFUL = 4;
MESSAGE = 5;
HINTS = 6;
SYMBOL = 7;
}
message Hint {
uint32 bits = 1; // hint function arch bits
uint64 offset = 2; // hint function offset
}
message Symbol {
string name = 1; // function name
string signature = 2; // function signature `type name(type arg0)`
string callconv = 3; // function calling convention
uint32 bits = 4; // function arch bits
}
/* client messages used to request info to the server */
message SectionHash {
uint32 size = 1; // binary section physical size
uint64 paddr = 2; // binary section physical address
bytes digest = 3; // binary section digest
}
message Binary {
string type = 1; // binary type
string os = 2; // binary os
repeated SectionHash sections = 3;
}
message Signature {
string arch = 1; // architecture name
uint32 bits = 2; // architecture bits
uint32 length = 3; // function length
bytes digest = 4; // function digest of the masked bytes
}
/* client messages used to upload info to the server */
message ShareSymbol {
Symbol symbol = 1; // symbol info
Signature signature = 2; // symbol signature
}
message ShareSection {
string name = 1; // binary section name
SectionHash section = 2; // binary section
repeated Hint hints = 3; // binary section hints
}
message ShareBin {
string type = 1; // binary type
string os = 2; // binary os
repeated ShareSection sections = 3; // binary sections
repeated ShareSymbol symbols = 4; // binary symbols
}
/* client request generics */
message Request {
string psk = 1; // authorization
uint32 version = 2; // protocol version
Route route = 3; // protocol route
bytes message = 4; // client encoded message based on the route
}
/* server messages used to reply to the client */
message Message {
string text = 1; // server text
}
message MatchHints {
repeated Hint hints = 1; // matched section hints
}
message Response {
Status status = 1; // server status
bytes message = 2; // server encoded message based on the status
}
+30
View File
@@ -0,0 +1,30 @@
# Copyright (c) 2016 NetDEF, Inc. and contributors
# Licensed under the MIT License:
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
@0xc0183dd65ffef0f3;
annotation nameinfix @0x85a8d86d736ba637 (file): Text;
annotation fieldgetset @0xf72bc690355d66de (file): Void;
annotation donotinclude @0x8c99797357b357e9 (file): UInt64;
annotation typedefto @0xcefaf27713042144 (struct, enum): Text;
annotation namespace @0xf2c035025fec7c2b (file): Text;
annotation extraheader @0xbadb496d09cf4612 (file): Text;
annotation extendedattribute @0xd187bca5c6844c24 (file): Text;
+12
View File
@@ -0,0 +1,12 @@
@0xd12a1c51fedd6c88;
annotation package(file) :Text;
annotation import(file) :Text;
annotation doc(struct, field, enum) :Text;
annotation tag(enumerant) :Text;
annotation notag(enumerant) :Void;
annotation customtype(field) :Text;
annotation name(struct, field, union, enum, enumerant, interface, method, param, annotation, const, group) :Text;
$package("gocp");
$import("capnproto.org/go/capnp/v3/std/go");
+7
View File
@@ -0,0 +1,7 @@
// SPDX-FileCopyrightText: 2026 RizinOrg <[email protected]>
// SPDX-License-Identifier: LGPL-3.0-only
// Package servicecapnp contains generated Cap'n Proto bindings.
//
//go:generate go run generate.go
package servicecapnp
+121
View File
@@ -0,0 +1,121 @@
//go:build ignore
// SPDX-FileCopyrightText: 2026 RizinOrg <[email protected]>
// SPDX-License-Identifier: LGPL-3.0-only
package main
import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
)
func main() {
serviceDir, err := schemaDir()
if err != nil {
fail(err)
}
capnpPath, err := exec.LookPath("capnp")
if err != nil {
fail(errors.New("capnp compiler was not found on PATH"))
}
capnpcGoPath, err := findCapnpcGo()
if err != nil {
fail(err)
}
schemaPath := filepath.Join(serviceDir, "service.capnp")
cmd := exec.Command(
capnpPath,
"compile",
"--src-prefix="+serviceDir,
"-I"+serviceDir,
"-o",
capnpcGoPath,
schemaPath,
)
cmd.Dir = serviceDir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fail(fmt.Errorf("failed to generate %s: %w", schemaPath, err))
}
}
func schemaDir() (string, error) {
_, file, _, ok := runtime.Caller(0)
if !ok {
return "", errors.New("failed to locate generator source file")
}
return filepath.Dir(file), nil
}
func findCapnpcGo() (string, error) {
if path, err := exec.LookPath("capnpc-go"); err == nil {
return path, nil
}
exeName := "capnpc-go"
if runtime.GOOS == "windows" {
exeName += ".exe"
}
for _, dir := range candidateBinDirs() {
path := filepath.Join(dir, exeName)
if info, err := os.Stat(path); err == nil && !info.IsDir() {
return path, nil
}
}
return "", errors.New("capnpc-go was not found on PATH, GOBIN, or GOPATH/bin")
}
func candidateBinDirs() []string {
seen := map[string]struct{}{}
dirs := []string{}
add := func(dir string) {
if dir == "" {
return
}
if _, ok := seen[dir]; ok {
return
}
seen[dir] = struct{}{}
dirs = append(dirs, dir)
}
add(os.Getenv("GOBIN"))
if gopath := os.Getenv("GOPATH"); gopath != "" {
for _, root := range filepath.SplitList(gopath) {
add(filepath.Join(root, "bin"))
}
}
if home, err := os.UserHomeDir(); err == nil {
add(filepath.Join(home, "go", "bin"))
}
if out, err := exec.Command("go", "env", "GOBIN").Output(); err == nil {
add(strings.TrimSpace(string(out)))
}
if out, err := exec.Command("go", "env", "GOPATH").Output(); err == nil {
for _, root := range filepath.SplitList(strings.TrimSpace(string(out))) {
add(filepath.Join(root, "bin"))
}
}
return dirs
}
func fail(err error) {
fmt.Fprintln(os.Stderr, "servicecapnp generator:", err)
os.Exit(1)
}
+140
View File
@@ -0,0 +1,140 @@
# SPDX-FileCopyrightText: 2026 RizinOrg <[email protected]>
# SPDX-License-Identifier: LGPL-3.0-only
using C = import "capnp/c.capnp";
using Go = import "capnp/go.capnp";
@0xa0f8f4ce58fdd4d6;
$C.fieldgetset;
$Go.package("servicecapnp");
$Go.import("rz-silhouette-server/servicecapnp");
enum SilCodec {
capnp @0;
}
enum SilRoute {
ping @0;
resolveProgram @1;
shareProgram @2;
}
enum SilStatus {
internalError @0;
clientBadPreSharedKey @1;
clientNotAuthorized @2;
versionMismatch @3;
message @4;
serverInfo @5;
resolveResult @6;
shareResult @7;
}
struct SilSectionHash {
size @0 :UInt32;
paddr @1 :UInt64;
digest @2 :Data;
name @3 :Text;
}
struct SilHint {
bits @0 :UInt32;
offset @1 :UInt64;
}
struct SilSymbol {
name @0 :Text;
signature @1 :Text;
callconv @2 :Text;
bits @3 :UInt32;
}
struct SilFunctionBundle {
addr @0 :UInt64;
size @1 :UInt32;
bits @2 :UInt32;
arch @3 :Text;
length @4 :UInt32;
digest @5 :Data;
sectionName @6 :Text;
sectionPaddr @7 :UInt64;
name @8 :Text;
signature @9 :Text;
callconv @10 :Text;
sectionOffset @11 :UInt64;
}
struct SilProgramBundle {
binaryType @0 :Text;
os @1 :Text;
arch @2 :Text;
bits @3 :UInt32;
binaryId @4 :Text;
sections @5 :List(SilSectionHash);
functions @6 :List(SilFunctionBundle);
}
struct SilPing {
reserved @0 :Bool;
}
struct SilResolveProgram {
program @0 :SilProgramBundle;
}
struct SilShareProgram {
program @0 :SilProgramBundle;
}
struct SilRequest {
psk @0 :Text;
version @1 :UInt32;
route @2 :SilRoute;
union {
ping @3 :SilPing;
resolveProgram @4 :SilResolveProgram;
shareProgram @5 :SilShareProgram;
}
}
struct SilMessage {
text @0 :Text;
}
struct SilServerInfo {
supportedCodecs @0 :List(SilCodec);
version @1 :UInt32;
tlsRequired @2 :Bool;
}
struct SilSymbolMatch {
addr @0 :UInt64;
symbol @1 :SilSymbol;
exact @2 :Bool;
matchedBinaryId @3 :Text;
matchedBy @4 :Text;
offset @5 :UInt64;
size @6 :UInt32;
}
struct SilResolveResult {
hints @0 :List(SilHint);
symbols @1 :List(SilSymbolMatch);
}
struct SilShareResult {
binaryId @0 :Text;
}
struct SilResponse {
status @0 :SilStatus;
union {
message @1 :SilMessage;
serverInfo @2 :SilServerInfo;
resolveResult @3 :SilResolveResult;
shareResult @4 :SilShareResult;
}
}
+1
View File
@@ -0,0 +1 @@
1007500102010111054a110801ff64656d6f2d70736b000001
+252
View File
@@ -0,0 +1,252 @@
// SPDX-FileCopyrightText: 2026 RizinOrg <[email protected]>
// SPDX-License-Identifier: LGPL-3.0-only
package main
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"strings"
)
const (
PROTOCOL_VERSION = uint32(1)
)
type Hint struct {
Bits uint32 `json:"bits"`
Offset uint64 `json:"offset"`
}
type Symbol struct {
Name string `json:"name,omitempty"`
Signature string `json:"signature,omitempty"`
Callconv string `json:"callconv,omitempty"`
Bits uint32 `json:"bits,omitempty"`
}
type SectionHash struct {
Size uint32 `json:"size"`
Paddr uint64 `json:"paddr"`
Digest []byte `json:"digest,omitempty"`
}
type Signature struct {
Arch string `json:"arch,omitempty"`
Bits uint32 `json:"bits,omitempty"`
Length uint32 `json:"length,omitempty"`
Digest []byte `json:"digest,omitempty"`
}
type ShareSymbol struct {
Symbol *Symbol `json:"symbol,omitempty"`
Signature *Signature `json:"signature,omitempty"`
}
type ShareSection struct {
Name string `json:"name,omitempty"`
Section *SectionHash `json:"section,omitempty"`
Hints []*Hint `json:"hints,omitempty"`
}
type ShareBin struct {
Type string `json:"type,omitempty"`
Os string `json:"os,omitempty"`
Sections []*ShareSection `json:"sections,omitempty"`
Symbols []*ShareSymbol `json:"symbols,omitempty"`
}
type SymbolRecord struct {
Name string `json:"name,omitempty"`
Signature string `json:"signature,omitempty"`
Callconv string `json:"callconv,omitempty"`
Bits uint32 `json:"bits,omitempty"`
}
type SectionRecord struct {
Name string `json:"name,omitempty"`
Size uint32 `json:"size"`
Paddr uint64 `json:"paddr"`
Digest []byte `json:"digest,omitempty"`
}
type FunctionRecord struct {
Addr uint64 `json:"addr"`
Size uint32 `json:"size"`
Bits uint32 `json:"bits,omitempty"`
Arch string `json:"arch,omitempty"`
Length uint32 `json:"length,omitempty"`
Digest []byte `json:"digest,omitempty"`
SectionName string `json:"section_name,omitempty"`
SectionPaddr uint64 `json:"section_paddr"`
SectionOffset uint64 `json:"section_offset"`
Name string `json:"name,omitempty"`
Signature string `json:"signature,omitempty"`
Callconv string `json:"callconv,omitempty"`
}
type ProgramBundle struct {
BinaryType string `json:"binary_type,omitempty"`
OS string `json:"os,omitempty"`
Arch string `json:"arch,omitempty"`
Bits uint32 `json:"bits,omitempty"`
BinaryID string `json:"binary_id,omitempty"`
Sections []SectionRecord `json:"sections,omitempty"`
Functions []FunctionRecord `json:"functions,omitempty"`
}
type HintMatch struct {
Bits uint32 `json:"bits"`
Offset uint64 `json:"offset"`
}
type SymbolMatchRecord struct {
Addr uint64 `json:"addr"`
Symbol SymbolRecord `json:"symbol"`
Exact bool `json:"exact"`
MatchedBinaryID string `json:"matched_binary_id,omitempty"`
MatchedBy string `json:"matched_by,omitempty"`
Offset uint64 `json:"offset,omitempty"`
Size uint32 `json:"size,omitempty"`
}
type ResolveProgramResult struct {
Hints []HintMatch `json:"hints,omitempty"`
Symbols []SymbolMatchRecord `json:"symbols,omitempty"`
}
type ShareProgramResult struct {
BinaryID string `json:"binary_id,omitempty"`
}
func symbolRecordFromProto(sym *Symbol) SymbolRecord {
if sym == nil {
return SymbolRecord{}
}
return SymbolRecord{
Name: sym.Name,
Signature: sym.Signature,
Callconv: sym.Callconv,
Bits: sym.Bits,
}
}
func (s SymbolRecord) Proto() *Symbol {
if s == (SymbolRecord{}) {
return nil
}
return &Symbol{
Name: s.Name,
Signature: s.Signature,
Callconv: s.Callconv,
Bits: s.Bits,
}
}
func normalizeSymbolRecord(sym SymbolRecord) SymbolRecord {
sym.Name = sanitizeSymbol(sym.Name)
sym.Signature = strings.TrimSpace(sym.Signature)
sym.Callconv = sanitizeWord(sym.Callconv, "")
if sym.Bits > 1024 {
sym.Bits = 0
}
return sym
}
func normalizeProgramBundle(bundle ProgramBundle) ProgramBundle {
bundle.BinaryType = sanitizeWord(bundle.BinaryType, GENERIC_DB)
bundle.OS = sanitizeWord(bundle.OS, GENERIC_DB)
bundle.Arch = sanitizeWord(bundle.Arch, GENERIC_DB)
for i := range bundle.Sections {
bundle.Sections[i].Name = strings.TrimSpace(bundle.Sections[i].Name)
bundle.Sections[i].Digest = append([]byte(nil), bundle.Sections[i].Digest...)
}
for i := range bundle.Functions {
fn := &bundle.Functions[i]
fn.Arch = sanitizeWord(fn.Arch, bundle.Arch)
fn.SectionName = strings.TrimSpace(fn.SectionName)
fn.Name = strings.TrimSpace(fn.Name)
fn.Signature = strings.TrimSpace(fn.Signature)
fn.Callconv = sanitizeWord(fn.Callconv, "")
fn.Digest = append([]byte(nil), fn.Digest...)
if fn.Length < 1 {
fn.Length = fn.Size
}
if fn.Bits < 1 {
fn.Bits = bundle.Bits
}
}
if bundle.BinaryID == "" {
bundle.BinaryID = computeBinaryID(bundle)
}
return bundle
}
func computeBinaryID(bundle ProgramBundle) string {
type manifestSection struct {
Name string `json:"name,omitempty"`
Size uint32 `json:"size"`
Paddr uint64 `json:"paddr"`
Digest string `json:"digest,omitempty"`
}
type manifestFunction struct {
Addr uint64 `json:"addr"`
Size uint32 `json:"size"`
Bits uint32 `json:"bits,omitempty"`
Arch string `json:"arch,omitempty"`
Length uint32 `json:"length,omitempty"`
Digest string `json:"digest,omitempty"`
SectionName string `json:"section_name,omitempty"`
SectionPaddr uint64 `json:"section_paddr"`
SectionOffset uint64 `json:"section_offset"`
Name string `json:"name,omitempty"`
Signature string `json:"signature,omitempty"`
Callconv string `json:"callconv,omitempty"`
}
type manifest struct {
BinaryType string `json:"binary_type,omitempty"`
OS string `json:"os,omitempty"`
Arch string `json:"arch,omitempty"`
Bits uint32 `json:"bits,omitempty"`
Sections []manifestSection `json:"sections,omitempty"`
Functions []manifestFunction `json:"functions,omitempty"`
}
out := manifest{
BinaryType: bundle.BinaryType,
OS: bundle.OS,
Arch: bundle.Arch,
Bits: bundle.Bits,
Sections: make([]manifestSection, 0, len(bundle.Sections)),
Functions: make([]manifestFunction, 0, len(bundle.Functions)),
}
for _, sec := range bundle.Sections {
out.Sections = append(out.Sections, manifestSection{
Name: sec.Name,
Size: sec.Size,
Paddr: sec.Paddr,
Digest: hex.EncodeToString(sec.Digest),
})
}
for _, fn := range bundle.Functions {
out.Functions = append(out.Functions, manifestFunction{
Addr: fn.Addr,
Size: fn.Size,
Bits: fn.Bits,
Arch: fn.Arch,
Length: fn.Length,
Digest: hex.EncodeToString(fn.Digest),
SectionName: fn.SectionName,
SectionPaddr: fn.SectionPaddr,
SectionOffset: fn.SectionOffset,
Name: fn.Name,
Signature: fn.Signature,
Callconv: fn.Callconv,
})
}
body, _ := json.Marshal(out)
sum := sha256.Sum256(body)
return hex.EncodeToString(sum[:])
}
+1 -1
View File
@@ -11,7 +11,7 @@ import (
)
var (
SYMBOLFILTER = regexp.MustCompile(`[^a-zA-Z0-9:.]+`)
SYMBOLFILTER = regexp.MustCompile(`[^a-zA-Z0-9:._]+`)
NONALPHANUMERIC = regexp.MustCompile(`[^a-z0-9]+`)
AUTH_FAIL = errors.New("auth: failed to authenticate")
TCP_PKT_TOO_BIG = errors.New("tcp: packet size is too big")