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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions