Skip to content

Commit 878a8a3

Browse files
committed
feat: validate get clarity metadata key format
1 parent 3aef92d commit 878a8a3

File tree

4 files changed

+215
-22
lines changed

4 files changed

+215
-22
lines changed

clarity/src/vm/database/clarity_db.rs

Lines changed: 102 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,68 @@ pub enum StoreType {
7676
PoxUnlockHeight = 0x15,
7777
}
7878

79+
impl TryFrom<&str> for StoreType {
80+
type Error = String;
81+
82+
fn try_from(value: &str) -> core::result::Result<Self, Self::Error> {
83+
use self::StoreType::*;
84+
85+
let hex_value = u8::from_str_radix(value, 10).map_err(|e| e.to_string())?;
86+
match hex_value {
87+
0x00 => Ok(DataMap),
88+
0x01 => Ok(Variable),
89+
0x02 => Ok(FungibleToken),
90+
0x03 => Ok(CirculatingSupply),
91+
0x04 => Ok(NonFungibleToken),
92+
0x05 => Ok(DataMapMeta),
93+
0x06 => Ok(VariableMeta),
94+
0x07 => Ok(FungibleTokenMeta),
95+
0x08 => Ok(NonFungibleTokenMeta),
96+
0x09 => Ok(Contract),
97+
0x10 => Ok(SimmedBlock),
98+
0x11 => Ok(SimmedBlockHeight),
99+
0x12 => Ok(Nonce),
100+
0x13 => Ok(STXBalance),
101+
0x14 => Ok(PoxSTXLockup),
102+
0x15 => Ok(PoxUnlockHeight),
103+
_ => Err("Invalid StoreType".into()),
104+
}
105+
}
106+
}
107+
108+
pub enum ContractDataVarName {
109+
Contract,
110+
ContractSize,
111+
ContractSrc,
112+
ContractDataSize,
113+
}
114+
115+
impl ContractDataVarName {
116+
pub fn as_str(&self) -> &str {
117+
match self {
118+
Self::Contract => "contract",
119+
Self::ContractSize => "contract-size",
120+
Self::ContractSrc => "contract-src",
121+
Self::ContractDataSize => "contract-data-size",
122+
}
123+
}
124+
}
125+
126+
impl TryFrom<&str> for ContractDataVarName {
127+
type Error = String;
128+
129+
fn try_from(value: &str) -> core::result::Result<Self, Self::Error> {
130+
use self::ContractDataVarName::*;
131+
match value {
132+
"contract" => Ok(Contract),
133+
"contract-size" => Ok(ContractSize),
134+
"contract-src" => Ok(ContractSrc),
135+
"contract-data-size" => Ok(ContractDataSize),
136+
_ => Err("Invalid ContractDataVarName".into()),
137+
}
138+
}
139+
}
140+
79141
pub struct ClarityDatabase<'a> {
80142
pub store: RollbackWrapper<'a>,
81143
headers_db: &'a dyn HeadersDB,
@@ -576,12 +638,18 @@ impl<'a> ClarityDatabase<'a> {
576638
self.store
577639
.prepare_for_contract_metadata(contract_identifier, hash)?;
578640
// insert contract-size
579-
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract-size");
641+
let key = ClarityDatabase::make_metadata_key(
642+
StoreType::Contract,
643+
ContractDataVarName::ContractSize.as_str(),
644+
);
580645
self.insert_metadata(contract_identifier, &key, &(contract_content.len() as u64))?;
581646

582647
// insert contract-src
583648
if STORE_CONTRACT_SRC_INTERFACE {
584-
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract-src");
649+
let key = ClarityDatabase::make_metadata_key(
650+
StoreType::Contract,
651+
ContractDataVarName::ContractSrc.as_str(),
652+
);
585653
self.insert_metadata(contract_identifier, &key, &contract_content.to_string())?;
586654
}
587655
Ok(())
@@ -591,7 +659,10 @@ impl<'a> ClarityDatabase<'a> {
591659
&mut self,
592660
contract_identifier: &QualifiedContractIdentifier,
593661
) -> Option<String> {
594-
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract-src");
662+
let key = ClarityDatabase::make_metadata_key(
663+
StoreType::Contract,
664+
ContractDataVarName::ContractSrc.as_str(),
665+
);
595666
self.fetch_metadata(contract_identifier, &key)
596667
.ok()
597668
.flatten()
@@ -700,15 +771,21 @@ impl<'a> ClarityDatabase<'a> {
700771
&mut self,
701772
contract_identifier: &QualifiedContractIdentifier,
702773
) -> Result<u64> {
703-
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract-size");
774+
let key = ClarityDatabase::make_metadata_key(
775+
StoreType::Contract,
776+
ContractDataVarName::ContractSize.as_str(),
777+
);
704778
let contract_size: u64 =
705779
self.fetch_metadata(contract_identifier, &key)?
706780
.ok_or_else(|| {
707781
InterpreterError::Expect(
708782
"Failed to read non-consensus contract metadata, even though contract exists in MARF."
709783
.into())
710784
})?;
711-
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract-data-size");
785+
let key = ClarityDatabase::make_metadata_key(
786+
StoreType::Contract,
787+
ContractDataVarName::ContractDataSize.as_str(),
788+
);
712789
let data_size: u64 = self
713790
.fetch_metadata(contract_identifier, &key)?
714791
.ok_or_else(|| {
@@ -727,7 +804,10 @@ impl<'a> ClarityDatabase<'a> {
727804
contract_identifier: &QualifiedContractIdentifier,
728805
data_size: u64,
729806
) -> Result<()> {
730-
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract-size");
807+
let key = ClarityDatabase::make_metadata_key(
808+
StoreType::Contract,
809+
ContractDataVarName::ContractSize.as_str(),
810+
);
731811
let contract_size: u64 =
732812
self.fetch_metadata(contract_identifier, &key)?
733813
.ok_or_else(|| {
@@ -737,7 +817,10 @@ impl<'a> ClarityDatabase<'a> {
737817
})?;
738818
contract_size.cost_overflow_add(data_size)?;
739819

740-
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract-data-size");
820+
let key = ClarityDatabase::make_metadata_key(
821+
StoreType::Contract,
822+
ContractDataVarName::ContractDataSize.as_str(),
823+
);
741824
self.insert_metadata(contract_identifier, &key, &data_size)?;
742825
Ok(())
743826
}
@@ -747,21 +830,30 @@ impl<'a> ClarityDatabase<'a> {
747830
contract_identifier: &QualifiedContractIdentifier,
748831
contract: Contract,
749832
) -> Result<()> {
750-
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract");
833+
let key = ClarityDatabase::make_metadata_key(
834+
StoreType::Contract,
835+
ContractDataVarName::Contract.as_str(),
836+
);
751837
self.insert_metadata(contract_identifier, &key, &contract)?;
752838
Ok(())
753839
}
754840

755841
pub fn has_contract(&mut self, contract_identifier: &QualifiedContractIdentifier) -> bool {
756-
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract");
842+
let key = ClarityDatabase::make_metadata_key(
843+
StoreType::Contract,
844+
ContractDataVarName::Contract.as_str(),
845+
);
757846
self.store.has_metadata_entry(contract_identifier, &key)
758847
}
759848

760849
pub fn get_contract(
761850
&mut self,
762851
contract_identifier: &QualifiedContractIdentifier,
763852
) -> Result<Contract> {
764-
let key = ClarityDatabase::make_metadata_key(StoreType::Contract, "contract");
853+
let key = ClarityDatabase::make_metadata_key(
854+
StoreType::Contract,
855+
ContractDataVarName::Contract.as_str(),
856+
);
765857
let mut data: Contract = self.fetch_metadata(contract_identifier, &key)?
766858
.ok_or_else(|| InterpreterError::Expect(
767859
"Failed to read non-consensus contract metadata, even though contract exists in MARF."

stackslib/src/net/api/getclaritymetadata.rs

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
// along with this program. If not, see <http://www.gnu.org/licenses/>.
1616

1717
use clarity::vm::clarity::ClarityConnection;
18+
use clarity::vm::database::clarity_db::ContractDataVarName;
19+
use clarity::vm::database::StoreType;
1820
use clarity::vm::representations::{CONTRACT_NAME_REGEX_STRING, STANDARD_PRINCIPAL_REGEX_STRING};
1921
use clarity::vm::types::QualifiedContractIdentifier;
2022
use clarity::vm::ContractName;
@@ -37,7 +39,7 @@ lazy_static! {
3739
static ref CLARITY_NAME_NO_BOUNDARIES_REGEX_STRING: String =
3840
"[a-zA-Z]([a-zA-Z0-9]|[-_!?+<>=/*])*|[-+=/*]|[<>]=?".into();
3941
static ref METADATA_KEY_REGEX_STRING: String = format!(
40-
r"vm-metadata::\d+::(contract|contract-size|contract-src|contract-data-size|({}))",
42+
r"vm-metadata::(?P<data_type>(\d{{1,2}}))::(?P<var_name>(contract|contract-size|contract-src|contract-data-size|({})))",
4143
*CLARITY_NAME_NO_BOUNDARIES_REGEX_STRING,
4244
);
4345
}
@@ -81,8 +83,6 @@ impl HttpRequest for RPCGetClarityMetadataRequestHandler {
8183
"/v2/clarity/metadata/:principal/:contract_name/:clarity_metadata_key"
8284
}
8385

84-
/// Try to decode this request.
85-
/// There's nothing to load here, so just make sure the request is well-formed.
8686
fn try_parse_request(
8787
&mut self,
8888
preamble: &HttpRequestPreamble,
@@ -98,13 +98,43 @@ impl HttpRequest for RPCGetClarityMetadataRequestHandler {
9898

9999
let contract_identifier = request::get_contract_address(captures, "address", "contract")?;
100100

101-
let metadata_key = if let Some(key_str) = captures.name("clarity_metadata_key") {
102-
key_str.as_str().to_string()
103-
} else {
104-
return Err(Error::Http(
105-
404,
106-
"Missing `clarity_metadata_key`".to_string(),
107-
));
101+
// Validate that the metadata key is well-formed. It must be of data type:
102+
// DataMapMeta (5) | VariableMeta (6) | FungibleTokenMeta (7) | NonFungibleTokenMeta (8)
103+
// or Contract (9) followed by a valid contract metadata name
104+
match captures
105+
.name("data_type")
106+
.and_then(|data_type| StoreType::try_from(data_type.as_str()).ok())
107+
{
108+
Some(data_type) => match data_type {
109+
StoreType::DataMapMeta
110+
| StoreType::VariableMeta
111+
| StoreType::FungibleTokenMeta
112+
| StoreType::NonFungibleTokenMeta => {}
113+
StoreType::Contract => {
114+
if captures
115+
.name("var_name")
116+
.and_then(|var_name| ContractDataVarName::try_from(var_name.as_str()).ok())
117+
.is_none()
118+
{
119+
return Err(Error::DecodeError("Invalid metadata var name".to_string()));
120+
}
121+
}
122+
_ => {
123+
return Err(Error::DecodeError("Invalid metadata type".to_string()));
124+
}
125+
},
126+
None => {
127+
return Err(Error::DecodeError("Invalid metadata type".to_string()));
128+
}
129+
}
130+
131+
let metadata_key = match captures.name("clarity_metadata_key") {
132+
Some(key_str) => key_str.as_str().to_string(),
133+
None => {
134+
return Err(Error::DecodeError(
135+
"Missing `clarity_metadata_key`".to_string(),
136+
));
137+
}
108138
};
109139

110140
self.contract_identifier = Some(contract_identifier);

stackslib/src/net/api/tests/getclaritymarfvalue.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ fn test_try_make_response() {
8989

9090
let mut requests = vec![];
9191

92-
// query existing
92+
// query existing marf value
9393
let request = StacksHttpRequest::new_getclaritymarf(
9494
addr.into(),
9595
TrieHash::from_key("vm::ST2DS4MSWSGJ3W9FBC6BVT0Y92S345HY8N3T6AV7R.hello-world::1::bar"),

stackslib/src/net/api/tests/getclaritymetadata.rs

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@ use stacks_common::types::Address;
2626
use super::test_rpc;
2727
use crate::net::api::*;
2828
use crate::net::connection::ConnectionOptions;
29+
use crate::net::http::Error as HttpError;
2930
use crate::net::httpcore::{
3031
HttpPreambleExtensions, HttpRequestContentsExtensions, RPCRequestHandler, StacksHttp,
3132
StacksHttpRequest,
3233
};
33-
use crate::net::{ProtocolFamily, TipRequest};
34+
use crate::net::{Error as NetError, ProtocolFamily, TipRequest};
3435

3536
#[test]
3637
fn test_try_parse_request() {
@@ -85,6 +86,76 @@ fn test_try_parse_request() {
8586
assert!(handler.clarity_metadata_key.is_none());
8687
}
8788

89+
#[test]
90+
fn test_try_parse_invalid_store_type() {
91+
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 33333);
92+
let mut http = StacksHttp::new(addr.clone(), &ConnectionOptions::default());
93+
94+
let request = StacksHttpRequest::new_getclaritymetadata(
95+
addr.into(),
96+
StacksAddress::from_string("ST2DS4MSWSGJ3W9FBC6BVT0Y92S345HY8N3T6AV7R").unwrap(),
97+
"hello-world".try_into().unwrap(),
98+
"vm-metadata::2::contract-size".to_string(),
99+
TipRequest::SpecificTip(StacksBlockId([0x22; 32])),
100+
);
101+
assert_eq!(
102+
request.contents().tip_request(),
103+
TipRequest::SpecificTip(StacksBlockId([0x22; 32]))
104+
);
105+
let bytes = request.try_serialize().unwrap();
106+
107+
let (parsed_preamble, offset) = http.read_preamble(&bytes).unwrap();
108+
let mut handler = getclaritymetadata::RPCGetClarityMetadataRequestHandler::new();
109+
let parsed_request_err = http
110+
.handle_try_parse_request(
111+
&mut handler,
112+
&parsed_preamble.expect_request(),
113+
&bytes[offset..],
114+
)
115+
.unwrap_err();
116+
117+
assert_eq!(
118+
parsed_request_err,
119+
HttpError::DecodeError("Invalid metadata type".to_string()).into()
120+
);
121+
handler.restart();
122+
}
123+
124+
#[test]
125+
fn test_try_parse_invalid_contract_metadata_var_name() {
126+
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 33333);
127+
let mut http = StacksHttp::new(addr.clone(), &ConnectionOptions::default());
128+
129+
let request = StacksHttpRequest::new_getclaritymetadata(
130+
addr.into(),
131+
StacksAddress::from_string("ST2DS4MSWSGJ3W9FBC6BVT0Y92S345HY8N3T6AV7R").unwrap(),
132+
"hello-world".try_into().unwrap(),
133+
"vm-metadata::9::contract-invalid-key".to_string(),
134+
TipRequest::SpecificTip(StacksBlockId([0x22; 32])),
135+
);
136+
assert_eq!(
137+
request.contents().tip_request(),
138+
TipRequest::SpecificTip(StacksBlockId([0x22; 32]))
139+
);
140+
let bytes = request.try_serialize().unwrap();
141+
142+
let (parsed_preamble, offset) = http.read_preamble(&bytes).unwrap();
143+
let mut handler = getclaritymetadata::RPCGetClarityMetadataRequestHandler::new();
144+
let parsed_request_err = http
145+
.handle_try_parse_request(
146+
&mut handler,
147+
&parsed_preamble.expect_request(),
148+
&bytes[offset..],
149+
)
150+
.unwrap_err();
151+
152+
assert_eq!(
153+
parsed_request_err,
154+
HttpError::DecodeError("Invalid metadata var name".to_string()).into()
155+
);
156+
handler.restart();
157+
}
158+
88159
#[test]
89160
fn test_try_parse_request_for_analysis() {
90161
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 33333);

0 commit comments

Comments
 (0)