mirror of
https://github.com/luxfi/chains.git
synced 2026-09-24 00:26:00 +00:00
Identify a GPU plugin before trusting it, and load only from a named place
A GPU plugin replaces a VM's state transitions wholesale. Three of the four
seams reached one by dlsym'ing launcher names out of a library nothing had
identified, and all four would fall back to a bare filename — which makes the
dynamic loader's default search path, and in bridgevm's case the working
directory of whoever started the node, the thing that decides which code
computes consensus state.
A library exporting the five bridgevm launcher names and nothing else,
compiled from twenty lines of C that return 0:
before, LUX_GPU_PLUGIN_DIR=/tmp/rogue AutoBackend = hip
before, no env, cwd holds the .so AutoBackend = hip
after, either AutoBackend = none
The plugin header says a loader must check abi_version and vtbl_size. aivm,
bridgevm and mpcvm now do so before resolving a single launcher: a name was
the only thing being matched, and a name is not a contract.
Bare filenames and the CWD entry are gone. What is left is LUX_GPU_PLUGIN_DIR
and LUXCPP_PREFIX — places an operator named. Unset means no plugin, which is
the configuration every box without a GPU is already in.
Cherry-picked from 634df5e, whose quantumvm hunk is dropped: that seam's file
was deleted in 409d0e4 and the chain no longer loads a plugin.
Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -34,9 +34,12 @@ package aivm
|
||||
#cgo darwin LDFLAGS: -ldl
|
||||
#cgo linux LDFLAGS: -ldl
|
||||
|
||||
#cgo CFLAGS: -I${SRCDIR}/../internal/luxgpu/include
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// Six host-launcher trampolines — invoked by Go via cgo. The function
|
||||
// pointer is opaque (void*); the cgo bridge casts it to the expected
|
||||
@@ -136,6 +139,27 @@ static int call_aivm_proof_verify(void* fn, const void* ops, void* results,
|
||||
return ((aivm_proof_verify_fn)fn)(ops, results, op_count, NULL);
|
||||
}
|
||||
|
||||
#include "lux/gpu/backend_plugin.h"
|
||||
|
||||
// aivm_plugin_trusted resolves lux_gpu_backend_init in an already-dlopen'd
|
||||
// library, runs it, and checks the two cookies the plugin header requires a
|
||||
// loader to check: abi_version must be the one this build was compiled
|
||||
// against, and vtbl_size must be our sizeof. Without it, dlsym'ing a launcher
|
||||
// by name trusts a library nothing has identified — the name is the only thing
|
||||
// being matched, and a name is not a contract. quantumvm already does this.
|
||||
//
|
||||
// 0 trusted | 1 no init symbol | 2 init false | 3 abi | 4 vtbl_size
|
||||
static int aivm_plugin_trusted(void* h) {
|
||||
void* sym = dlsym(h, LUX_GPU_BACKEND_INIT_SYMBOL);
|
||||
if (sym == NULL) return 1;
|
||||
lux_gpu_backend_desc desc;
|
||||
memset(&desc, 0, sizeof desc);
|
||||
if (!((lux_gpu_backend_init_fn)sym)(&desc)) return 2;
|
||||
if (desc.abi_version != LUX_GPU_BACKEND_ABI_VERSION) return 3;
|
||||
if (desc.vtbl_size != (uint32_t)sizeof(lux_gpu_backend_vtbl)) return 4;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// dlopen / dlsym wrappers — kept here so backend.go can stay pure Go.
|
||||
static void* lux_dlopen(const char* path) {
|
||||
return dlopen(path, RTLD_NOW | RTLD_LOCAL);
|
||||
@@ -508,6 +532,14 @@ func openGPUBackend(kind BackendKind, path string) (*GPUBackend, error) {
|
||||
return nil, fmt.Errorf("aivm: dlopen(%s): %s", path, C.GoString(C.lux_dlerror()))
|
||||
}
|
||||
|
||||
// Identify the library before trusting any symbol in it.
|
||||
if rc := C.aivm_plugin_trusted(handle); rc != 0 {
|
||||
C.lux_dlclose(handle)
|
||||
return nil, fmt.Errorf(
|
||||
"aivm: %s is not a lux GPU plugin this build can use (check %d)",
|
||||
path, int(rc))
|
||||
}
|
||||
|
||||
backendName := kind.String() // cuda / hip / metal / vulkan / webgpu
|
||||
|
||||
resolve := func(suffix string) (unsafe.Pointer, error) {
|
||||
|
||||
+4
-5
@@ -82,9 +82,11 @@ func platformCandidates() []backendCandidate {
|
||||
// - LUX_GPU_PLUGIN_DIR/build/metal-only/backends/<subdir>/<filename>
|
||||
// - LUX_GPU_PLUGIN_DIR/build/vulkan-m1/backends/<subdir>/<filename>
|
||||
// - LUXCPP_PREFIX/lib/<filename>
|
||||
// - bare filename (DT_RUNPATH / DT_RPATH / system search)
|
||||
//
|
||||
// Bare last so an explicit env override always wins.
|
||||
// Every entry is somewhere an operator named. There is deliberately no bare
|
||||
// filename: with one the dynamic loader searches its own default path, and a
|
||||
// GPU plugin replaces aivm's state transitions wholesale, so a library found
|
||||
// rather than named would be deciding consensus state.
|
||||
func candidatePaths(c backendCandidate) []string {
|
||||
var paths []string
|
||||
if dir := os.Getenv("LUX_GPU_PLUGIN_DIR"); dir != "" {
|
||||
@@ -107,9 +109,6 @@ func candidatePaths(c backendCandidate) []string {
|
||||
if dir := os.Getenv("LUXCPP_PREFIX"); dir != "" {
|
||||
paths = append(paths, filepath.Join(dir, "lib", c.filename))
|
||||
}
|
||||
// Bare name — fall back to the loader's default search (DT_RPATH on Linux,
|
||||
// @rpath on darwin, LD_LIBRARY_PATH / DYLD_LIBRARY_PATH set by user).
|
||||
paths = append(paths, c.filename)
|
||||
return paths
|
||||
}
|
||||
|
||||
|
||||
+49
-15
@@ -15,14 +15,16 @@ package bridgevm
|
||||
// public build to a private dep; dlopen + dlsym lets us probe at runtime
|
||||
// and degrade cleanly to ErrGPUNotAvailable when the plugin is absent.
|
||||
//
|
||||
// Search path for the plugin:
|
||||
// Search path for the plugin — every entry is somewhere an operator named:
|
||||
//
|
||||
// 1. $LUX_GPU_PLUGIN_DIR (explicit override, useful in tests / CI).
|
||||
// 1. $LUX_GPU_PLUGIN_DIR/ and $LUX_GPU_PLUGIN_DIR/<bk>/ (explicit override).
|
||||
// 2. $LUXCPP_PREFIX/lib/lux-gpu/ (the install tree).
|
||||
// 3. $LUXCPP_PREFIX/lib/ (legacy install tree).
|
||||
// 4. $HOME/work/the lux GPU plugin build/backends/<bk>/ (dev tree).
|
||||
// 5. The current working directory.
|
||||
// 6. The system loader's default path (dlopen with bare name).
|
||||
//
|
||||
// Neither variable set means no plugin, which is the configuration every box
|
||||
// without a GPU is already in. There is deliberately no working-directory
|
||||
// entry and no bare name: a plugin replaces bridgevm's state transitions
|
||||
// wholesale, so a library the loader found would be deciding consensus state.
|
||||
//
|
||||
// Probe order — cuda → hip → metal → vulkan → webgpu. The first plugin that
|
||||
// resolves all five symbols wins and sets activeBackend. Subsequent probes
|
||||
@@ -38,10 +40,13 @@ package bridgevm
|
||||
/*
|
||||
#cgo LDFLAGS: -ldl
|
||||
|
||||
#cgo CFLAGS: -I${SRCDIR}/../internal/luxgpu/include
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <string.h>
|
||||
|
||||
// Trampolines — we cannot call function pointers from Go directly through cgo.
|
||||
// The trampoline takes the function pointer (resolved via dlsym from Go) and
|
||||
@@ -122,6 +127,34 @@ static int call_transition(void* fn,
|
||||
(void*)0);
|
||||
}
|
||||
|
||||
#include "lux/gpu/backend_plugin.h"
|
||||
|
||||
// bvm_plugin_trusted resolves lux_gpu_backend_init in an already-dlopen'd
|
||||
// library, runs it, and checks the two cookies the plugin header requires a
|
||||
// loader to check: abi_version must be the one this build was compiled
|
||||
// against, and vtbl_size must be our sizeof. Without this, dlsym'ing a
|
||||
// launcher by name trusts a library nothing has identified — the name is the
|
||||
// only thing being matched, and a name is not a contract.
|
||||
//
|
||||
// quantumvm already does exactly this; bridgevm, aivm and mpcvm dlsym'd
|
||||
// blind. One way, and this is it.
|
||||
//
|
||||
// 0 — trusted
|
||||
// 1 — no lux_gpu_backend_init (not one of our plugins)
|
||||
// 2 — init returned false
|
||||
// 3 — abi_version mismatch
|
||||
// 4 — vtbl_size mismatch
|
||||
static int bvm_plugin_trusted(void* h) {
|
||||
void* sym = dlsym(h, LUX_GPU_BACKEND_INIT_SYMBOL);
|
||||
if (sym == NULL) return 1;
|
||||
lux_gpu_backend_desc desc;
|
||||
memset(&desc, 0, sizeof desc);
|
||||
if (!((lux_gpu_backend_init_fn)sym)(&desc)) return 2;
|
||||
if (desc.abi_version != LUX_GPU_BACKEND_ABI_VERSION) return 3;
|
||||
if (desc.vtbl_size != (uint32_t)sizeof(lux_gpu_backend_vtbl)) return 4;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// dl helpers — wrap dlopen / dlsym / dlerror so the Go side doesn't have to
|
||||
// deal with the C string lifetimes.
|
||||
|
||||
@@ -198,11 +231,6 @@ func probePlugin() {
|
||||
setActiveBackend(bk)
|
||||
return
|
||||
}
|
||||
// Bare-name fallback — let the dynamic loader use its default path.
|
||||
if tryLoad(bk, dsoBareName(bk)) {
|
||||
setActiveBackend(bk)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,11 +253,12 @@ func candidatePluginPaths(bk Backend) []string {
|
||||
paths = append(paths, filepath.Join(prefix, "lib", name))
|
||||
}
|
||||
|
||||
// 4) CWD — last resort before falling back to the loader default.
|
||||
if cwd, err := os.Getwd(); err == nil {
|
||||
paths = append(paths, filepath.Join(cwd, name))
|
||||
}
|
||||
|
||||
// Deliberately no CWD entry and no bare-name fallback. A GPU plugin
|
||||
// replaces bridgevm's state transitions wholesale, so where it comes from
|
||||
// has to be something an operator said, not something the loader found:
|
||||
// with a bare name the dynamic loader searches its default path, and with
|
||||
// CWD the working directory of whoever started the node decides which
|
||||
// code computes consensus state. Both are named above or nothing loads.
|
||||
return paths
|
||||
}
|
||||
|
||||
@@ -274,6 +303,11 @@ func tryLoad(bk Backend, path string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Identify the library before trusting any symbol in it.
|
||||
if rc := C.bvm_plugin_trusted(h); rc != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
prefix := "lux_" + bk.String() + "_bridgevm_"
|
||||
syms := map[string]*unsafe.Pointer{
|
||||
prefix + "signer_apply": &fnSignerApply,
|
||||
|
||||
+5
-3
@@ -87,9 +87,11 @@ func candidatesFor(kind GPUBackendKind) []string {
|
||||
}
|
||||
}
|
||||
|
||||
// Bare leaf names — dynamic-linker default search picks them up if
|
||||
// LD_LIBRARY_PATH / DYLD_LIBRARY_PATH or /usr/local/lib has them.
|
||||
out = append(out, leaves...)
|
||||
// Deliberately no bare leaf names. With one the dynamic linker searches
|
||||
// its own default path, and a GPU plugin replaces mpcvm's ceremony and
|
||||
// key-share transitions wholesale — a library found rather than named
|
||||
// would be minting threshold-signing custody. Every entry above is
|
||||
// somewhere an operator said.
|
||||
return out
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,8 @@ package mpcvm
|
||||
#cgo darwin LDFLAGS: -ldl
|
||||
#cgo linux LDFLAGS: -ldl
|
||||
|
||||
#cgo CFLAGS: -I${SRCDIR}/../internal/luxgpu/include
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
@@ -190,6 +192,27 @@ static void* lux_dlsym(void* handle, const char* name) {
|
||||
// if every candidate fails. The dlopen mode is RTLD_NOW|RTLD_LOCAL: NOW so
|
||||
// any missing symbol fails fast at load time, LOCAL so the plugin's
|
||||
// internal symbols don't pollute the global namespace.
|
||||
#include "lux/gpu/backend_plugin.h"
|
||||
|
||||
// mpcvm_plugin_trusted resolves lux_gpu_backend_init in an already-dlopen'd
|
||||
// library, runs it, and checks the two cookies the plugin header requires a
|
||||
// loader to check: abi_version must be the one this build was compiled
|
||||
// against, and vtbl_size must be our sizeof. Without it, dlsym'ing a launcher
|
||||
// by name trusts a library nothing has identified — the name is the only thing
|
||||
// being matched, and a name is not a contract. quantumvm already does this.
|
||||
//
|
||||
// 0 trusted | 1 no init symbol | 2 init false | 3 abi | 4 vtbl_size
|
||||
static int mpcvm_plugin_trusted(void* h) {
|
||||
void* sym = dlsym(h, LUX_GPU_BACKEND_INIT_SYMBOL);
|
||||
if (sym == NULL) return 1;
|
||||
lux_gpu_backend_desc desc;
|
||||
memset(&desc, 0, sizeof desc);
|
||||
if (!((lux_gpu_backend_init_fn)sym)(&desc)) return 2;
|
||||
if (desc.abi_version != LUX_GPU_BACKEND_ABI_VERSION) return 3;
|
||||
if (desc.vtbl_size != (uint32_t)sizeof(lux_gpu_backend_vtbl)) return 4;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void* lux_dlopen_first(const char* a, const char* b, const char* c) {
|
||||
void* h = NULL;
|
||||
if (a && *a) { h = dlopen(a, RTLD_NOW|RTLD_LOCAL); if (h) return h; }
|
||||
@@ -790,6 +813,11 @@ func tryLoadPlugin(kind GPUBackendKind, candidates ...string) *GPUBackend {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Identify the library before trusting any symbol in it.
|
||||
if rc := C.mpcvm_plugin_trusted(h); rc != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
resolve := func(name string) unsafe.Pointer {
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
|
||||
Reference in New Issue
Block a user