Skip to content

Suggestion for new decode API forcing developers to explicitly write down all decode options/limits #785

@jakoblell

Description

@jakoblell

As of now we have quite a number of decoding APIs such as the following:

  • decode
  • decode_all
  • decode_all_with_depth_limit
  • decode_with_mem_limit

However some options/combinations are missing, for example there is no decode_all_with_mem_limit and it is not possible to combine a a depth limit with a mem limit (at least not without manually chaining MemTrackingInput and DepthTrackingInput). Additionally to that, the current API does not force developers to explicitly decide (and write down) whether the _all variant is needed and whether a depth limit and/or a mem limit is required. This can easily lead to security vulnerabilities such as using decode instead of decode_all (something which has happened in the past).

In order to improve the situation I'd suggest a new API to force developers to explicitly write down the choices (decode vs decode_all, with or without depth limit, with or without mem limit). The following code outlines how the API could look like. Regarding the choice to use separate types for all combinations: This is required to implement some options only for types implementing DecodeWithMemTracking - and it also allows the compiler to only include the implementation variants actually used.

use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode, Error, Input};

#[derive(Encode, Decode)]
struct Foo{
    a: u32,
    b: u32
}

fn main() {
    let foo = Foo{a: 1, b: 2};
    let encoded = foo.encode();
    let _decoded: Foo = DecodeMode::ForceDecodeAll.no_depth_limit().no_mem_limit().decode(&mut &encoded[..]).unwrap();
    // This will fail at compile time with the message "the trait bound `Foo: DecodeWithMemTracking` is not satisfied"
    // Correct since Foo does not implement/derive DecodeWithMemTracking
    // let _decoded: Foo = DecodeMode::ForceDecodeAll.no_depth_limit().with_mem_limit(1_000_000).decode(&mut &encoded[..]).unwrap();
}

pub enum DecodeMode{
  ForceDecodeAll,
  AllowPartialDecoding
}

impl DecodeMode{
    fn with_depth_limit(self, depth_limit: u32) -> DecodeModeWithDepthLimit{
        DecodeModeWithDepthLimit{
            decode_mode: self,
            depth_limit,
        }
    }
    fn no_depth_limit(self) -> DecodeModeNoDepthLimit{
        DecodeModeNoDepthLimit{
            decode_mode: self,
        }
    }
}

pub struct DecodeModeWithDepthLimit{
    decode_mode: DecodeMode,
    depth_limit: u32
}

impl DecodeModeWithDepthLimit{
    pub fn with_mem_limit(self, mem_limit: usize) -> DecodeModeDepthAndMemLimit{
        DecodeModeDepthAndMemLimit{
            decode_mode: self.decode_mode,
            depth_limit: self.depth_limit,
            mem_limit
        }
    }
    pub fn no_mem_limit(self) -> DecodeModeDepthLimitOnly{
        DecodeModeDepthLimitOnly{
            decode_mode: self.decode_mode,
            depth_limit: self.depth_limit
        }
    }
}

pub struct DecodeModeNoDepthLimit{
    decode_mode: DecodeMode
}

impl DecodeModeNoDepthLimit{
    pub fn with_mem_limit(self, mem_limit: usize) -> DecodeModeMemLimitOnly{
        DecodeModeMemLimitOnly{
            decode_mode: self.decode_mode,
            mem_limit
        }
    }
    pub fn no_mem_limit(self) -> DecodeModeNoLimits{
        DecodeModeNoLimits{
            decode_mode: self.decode_mode,
        }
    }
}


pub struct DecodeModeNoLimits{
    decode_mode: DecodeMode
}
impl DecodeModeNoLimits{
    fn decode<I,T>(&self, input: &mut I) -> Result<T, Error>
    where I: Input, T: Decode
    {
        let t = T::decode(input)?;
        // TODO: Check if input is empty based on self.decode_mode
        Ok(t)
    }
}
pub struct DecodeModeDepthLimitOnly{
    decode_mode: DecodeMode,
    depth_limit: u32
}

impl DecodeModeDepthLimitOnly{
    fn decode<I,T>(&self, input: &mut I) -> Result<T, Error>
    where I: Input, T: Decode
    {
        todo!()
    }
}

pub struct DecodeModeMemLimitOnly{
    decode_mode: DecodeMode,
    mem_limit: usize
}

impl DecodeModeMemLimitOnly{
    fn decode<I,T>(&self, input: &mut I) -> Result<T, Error>
    where I: Input, T: DecodeWithMemTracking
    {
        todo!()
    }
}
pub struct DecodeModeDepthAndMemLimit{
    decode_mode: DecodeMode,
    depth_limit: u32,
    mem_limit: usize
}

impl DecodeModeDepthAndMemLimit{
    fn decode<I,T>(&self, input: &mut I) -> Result<T, Error>
    where I: Input, T: DecodeWithMemTracking
    {
        todo!()
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions