Skip to content

Commit

Permalink
Merge pull request #935 from scrtlabs/analyze-code
Browse files Browse the repository at this point in the history
AnalyzeCode functionality
  • Loading branch information
liorbond authored May 30, 2022
2 parents 933fa72 + 37fc843 commit 6d65102
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 48 deletions.
39 changes: 39 additions & 0 deletions cosmwasm/packages/sgx-vm/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ pub struct CosmCache<S: Storage + 'static, A: Api + 'static, Q: Querier + 'stati
type_querier: PhantomData<Q>,
}

#[derive(PartialEq, Debug)]
pub struct AnalysisReport {
pub has_ibc_entry_points: bool,
pub required_features: HashSet<String>,
}

impl<S, A, Q> CosmCache<S, A, Q>
where
S: Storage + 'static,
Expand Down Expand Up @@ -115,6 +121,39 @@ where
}
}

pub fn deserialize_wasm(wasm_code: &[u8]) -> VmResult<Module> {
deserialize_buffer(wasm_code).map_err(|err| {
VmError::static_validation_err(format!(
"Wasm bytecode could not be deserialized. Deserialization error: \"{}\"",
err
))
})
}

/// Returns true if and only if all IBC entry points ([`REQUIRED_IBC_EXPORTS`])
/// exist as exported functions. This does not guarantee the entry points
/// are functional and for simplicity does not even check their signatures.
pub fn has_ibc_entry_points(module: &impl ExportInfo) -> bool {
let available_exports = module.exported_function_names(None);
REQUIRED_IBC_EXPORTS
.iter()
.all(|required| available_exports.contains(*required))
}

/// Performs static anlyzation on this Wasm without compiling or instantiating it.
///
/// Once the contract was stored via [`save_wasm`], this can be called at any point in time.
/// It does not depend on any caching of the contract.
pub fn analyze(&self, checksum: &Checksum) -> VmResult<AnalysisReport> {
// Here we could use a streaming deserializer to slightly improve performance. However, this way it is DRYer.
let wasm = self.load_wasm(checksum)?;
let module = deserialize_wasm(&wasm)?;
Ok(AnalysisReport {
has_ibc_entry_points: has_ibc_entry_points(&module),
required_features: required_features_from_module(&module),
})
}

/// Returns an Instance tied to a previously saved Wasm.
/// Depending on availability, this is either generated from a cached instance, a cached module or Wasm code.
pub fn get_instance(
Expand Down
20 changes: 20 additions & 0 deletions go-cosmwasm/api/bindings.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,22 @@ typedef struct Buffer {
uintptr_t cap;
} Buffer;

/**
* The result type of the FFI function analyze_code.
*
* Please note that the unmanaged vector in `required_features`
* has to be destroyed exactly once. When calling `analyze_code`
* from Go this is done via `C.destroy_unmanaged_vector`.
*/
typedef struct AnalysisReport {
bool has_ibc_entry_points;
/**
* An UTF-8 encoded comma separated list of reqired features.
* This is never None/nil.
*/
Buffer required_features;
} AnalysisReport;

typedef struct EnclaveRuntimeConfig {
uint8_t module_cache_size;
} EnclaveRuntimeConfig;
Expand Down Expand Up @@ -186,6 +202,10 @@ Buffer query(cache_t *cache,
uint64_t *gas_used,
Buffer *err);

AnalysisReport analyze_code(cache_t *cache,
ByteSliceView checksum,
Buffer *error_msg);

/**
* frees a cache reference
*
Expand Down
34 changes: 19 additions & 15 deletions go-cosmwasm/api/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,25 @@ func Query(
return receiveVector(res), uint64(gasUsed), nil
}

func AnalyzeCode(
cache Cache,
codeHash []byte,
) (*v1types.AnalysisReport, error) {
cs := sendSlice(codeHash)
defer runtime.KeepAlive(codeHash)
errMsg := C.Buffer{}
report, err := C.analyze_code(cache.ptr, cs, &errMsg)

if err != nil {
return nil, errorWithMessage(err, errMsg)
}
res := v1types.AnalysisReport{
HasIBCEntryPoints: bool(report.has_ibc_entry_points),
RequiredFeatures: string(receiveVector(report.required_features)),
}
return &res, nil
}

// KeyGen Send KeyGen request to enclave
func KeyGen() ([]byte, error) {
errmsg := C.Buffer{}
Expand Down Expand Up @@ -307,18 +326,3 @@ func errorWithMessage(err error, b C.Buffer) error {
}
return fmt.Errorf("%s", string(msg))
}

func AnalyzeCode(cache Cache, checksum []byte) (*v1types.AnalysisReport, error) {
cs := makeView(checksum)
defer runtime.KeepAlive(checksum)
errmsg := newUnmanagedVector(nil)
report, err := C.analyze_code(cache.ptr, cs, &errmsg)
if err != nil {
return nil, errorWithMessage(err, errmsg)
}
res := types.AnalysisReport{
HasIBCEntryPoints: bool(report.has_ibc_entry_points),
RequiredFeatures: string(copyAndDestroyUnmanagedVector(report.required_features)),
}
return &res, nil
}
37 changes: 4 additions & 33 deletions go-cosmwasm/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,41 +236,12 @@ func (w *Wasmer) Query(
return resp.Ok, gasUsed, nil
}

// AnalyzeCode will statically analyze the code.
// AnalyzeCode returns a report of static analysis of the wasm contract (uncompiled).
// This contract must have been stored in the cache previously (via Create).
// Only info currently returned is if it exposes all ibc entry points, but this may grow later
// Currently just reports if it exposes all IBC entry points.
func (w *Wasmer) AnalyzeCode(
codeHash []byte,
) (*v1types.AnalysisReport, error) {
data, gasUsed, err := api.AnalyzeCode(codeHash)
if err != nil {
return nil, nil, gasUsed, err
}

key := data[0:64]
data = data[64:]

var respV010 v010types.InitResult
jsonErrV010 := json.Unmarshal(data, &respV010)

if jsonErrV010 == nil {
// v0.10 response
if respV010.Err != nil {
return nil, nil, gasUsed, fmt.Errorf("%v", respV010.Err)
}
return respV010.Ok, key, gasUsed, nil
}

var respV1 v1types.ContractResult
jsonErrV1 := json.Unmarshal(data, &respV1)

if jsonErrV1 == nil {
// v1 response
if respV1.Err != "" {
return nil, nil, gasUsed, fmt.Errorf(respV1.Err)
}
return respV1.Ok, key, gasUsed, nil
}

// unidentified response 🤷
return nil, nil, gasUsed, fmt.Errorf("cannot detect response type, v0.10: %v or v0.16: %v", jsonErrV010, jsonErrV1)
return api.AnalyzeCode(w.cache, codeHash)
}
40 changes: 40 additions & 0 deletions go-cosmwasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,46 @@ fn do_query(
Ok(res?)
}

/// The result type of the FFI function analyze_code.
///
/// Please note that the unmanaged vector in `required_features`
/// has to be destroyed exactly once. When calling `analyze_code`
/// from Go this is done via `C.destroy_unmanaged_vector`.
#[repr(C)]
#[derive(Copy, Clone, Default, Debug, PartialEq)]
pub struct AnalysisReport {
pub has_ibc_entry_points: bool,
/// An UTF-8 encoded comma separated list of reqired features.
/// This is never None/nil.
pub required_features: Buffer,
}

#[no_mangle]
pub extern "C" fn analyze_code(
cache: *mut cache_t,
checksum: ByteSliceView,
error_msg: Option<&mut Buffer>,
) -> AnalysisReport {
let r = match to_cache(cache) {
Some(c) => catch_unwind(AssertUnwindSafe(move || do_analyze_code(c, checksum)))
.unwrap_or_else(|_| Err(Error::panic())),
None => Err(Error::unset_arg(CACHE_ARG)),
};
handle_c_error_default(r, error_msg)
}

fn do_analyze_code(
cache: &mut Cache<GoApi, GoStorage, GoQuerier>,
checksum: ByteSliceView,
) -> Result<AnalysisReport, Error> {
let checksum: Checksum = checksum
.read()
.ok_or_else(|| Error::unset_arg(CHECKSUM_ARG))?
.try_into()?;
let report = cache.analyze(&checksum)?;
Ok(report.into())
}

#[no_mangle]
pub extern "C" fn key_gen(err: Option<&mut Buffer>) -> Buffer {
match untrusted_key_gen() {
Expand Down

0 comments on commit 6d65102

Please sign in to comment.