Skip to content

fix(uptime_proof): validate version-list sizes before fixed-array writes#195

Open
raw391 wants to merge 2 commits into
Beldex-Coin:devfrom
raw391:fix/uptime-proof-version-bounds
Open

fix(uptime_proof): validate version-list sizes before fixed-array writes#195
raw391 wants to merge 2 commits into
Beldex-Coin:devfrom
raw391:fix/uptime-proof-version-bounds

Conversation

@raw391

@raw391 raw391 commented Jun 3, 2026

Copy link
Copy Markdown

The Proof::Proof(const std::string& serialized_proof) deserializer at src/cryptonote_core/uptime_proof.cpp:44-89 reads three version lists (v, sv, lv) from a bt-encoded peer-supplied blob into three fixed-size 3-element arrays (version[], storage_server_version[], belnet_version[]) without first checking that each bt_list has 3 entries:

const bt_list& bt_version = var::get<bt_list>(bt_proof.at("v"));
int k = 0;
for (bt_value const &i: bt_version){
  version[k++] = static_cast<uint16_t>(get_int<unsigned>(i));
}

A peer that sends a proof whose v list has more than 3 entries walks past the end of version[] into adjacent members of the Proof struct on the heap (the constructor is invoked via std::make_unique at cryptonote_core.cpp:1994). The constructor runs before any signature check downstream.

Patch adds an exact-size guard before each write loop, and wraps the make_unique call site in try/catch so a malformed proof becomes a rejected proof rather than a propagating exception:

     const bt_list& bt_version = var::get<bt_list>(bt_proof.at("v"));
+    if (bt_version.size() != version.size())
+      throw std::invalid_argument("uptime proof: invalid mnode_version list size");
     int k = 0;
     for (bt_value const &i: bt_version){
       version[k++] = static_cast<uint16_t>(get_int<unsigned>(i));
     }

(Plus matching guards for sv and lv.)

   bool core::handle_btencoded_uptime_proof(...)
   {
     crypto::x25519_public_key pkey = {};
-    auto proof = std::make_unique<uptime_proof::Proof>(req.proof);
+    std::unique_ptr<uptime_proof::Proof> proof;
+    try
+    {
+      proof = std::make_unique<uptime_proof::Proof>(req.proof);
+    }
+    catch (const std::exception &e)
+    {
+      MWARNING("Failed to parse uptime proof: " << e.what());
+      return false;
+    }
     proof->sig = tools::make_from_guts<crypto::signature>(req.sig);

Exact-match (!=) rather than > because a short list would leave the trailing array slots uninitialized.

The Proof deserializer at uptime_proof.cpp:44-89 reads three version
lists (v, sv, lv) from a peer-supplied bt-encoded blob and writes
each into a fixed-size array (version[], storage_server_version[],
belnet_version[]) without checking the list size first. A peer
that sends a list with more entries than the array can hold writes
past the end of the struct.

This deserializer runs on every incoming uptime proof before
any signature check downstream, so a malformed proof reaches
this loop without prior validation.

Adds an exact-size check before each write loop. The constructor
already wraps everything in try/catch, so the throw becomes a
cleanly-rejected proof rather than a corruption.
The invalid_argument throw added in the previous commit propagates
out of std::make_unique<uptime_proof::Proof>. Wrap at the caller in
core::handle_btencoded_uptime_proof so a malformed proof becomes a
rejected proof rather than a propagating exception.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants