#deserialize #serialization #serde #distributed #trait-object

nightly serde_traitobject

可序列化和反序列化的特例对象。该库实现了特例对象的序列化和反序列化,以便它们可以在运行相同二进制文件的其他进程之间传输。

18次发布

0.2.8 2022年11月25日
0.2.7 2020年7月14日
0.2.6 2020年6月25日
0.2.4 2019年11月24日
0.1.0 2018年7月23日

#806 in 编码

Download history 145/week @ 2024-03-12 116/week @ 2024-03-19 118/week @ 2024-03-26 163/week @ 2024-04-02 76/week @ 2024-04-09 114/week @ 2024-04-16 120/week @ 2024-04-23 91/week @ 2024-04-30 95/week @ 2024-05-07 105/week @ 2024-05-14 144/week @ 2024-05-21 181/week @ 2024-05-28 119/week @ 2024-06-04 98/week @ 2024-06-11 100/week @ 2024-06-18 79/week @ 2024-06-25

每月422次下载
用于 9 个crate(直接使用5个)

MIT/Apache

62KB
1K SLoC

serde_traitobject

Crates.io MIT / Apache 2.0 licensed Build Status

📖 文档 | 💬 聊天

可序列化和反序列化的特例对象。

该库实现了特例对象的序列化和反序列化,以便它们可以在运行相同二进制文件的其他进程之间传输。

例如,如果您有多个进程的分支,或者集群中每台机器上运行相同的二进制文件,该库允许您在它们之间传输特例对象。

任何特例都可以通过将其作为超特例添加此crate的#[serde)]Deserialize 特性,使其可序列化和反序列化

trait MyTrait: serde_traitobject::Serialize + serde_traitobject::Deserialize {
    fn my_method(&self);
}

#[derive(Serialize, Deserialize)]
struct Message {
    #[serde(with = "serde_traitobject")]
    message: Box<dyn MyTrait>,
}

// Woohoo, `Message` is now serializable!

就是这样!这两个特性自动应用于所有 T: serde::Serialize 和所有 T: serde::de::DeserializeOwned,只要您的特例的所有实现者本身都是可序列化的,那么您就可以开始了。

有两种方式来(反)序列化您的特例对象

  • 应用 #[serde)] 字段属性,它指示serde使用此crate的 serializedeserialize 函数;
  • Box、Rc 和 Arc 结构体,它们是它们stdlib对应结构的简单包装,能够自动处理序列化和反序列化,无需上述注解;

此外,还实现了几个方便的特质,它们扩展了它们的stdlib对应结构

这些特质会自动实现所有同时实现 serde::Serializeserde::de::DeserializeOwned 的stdlib对应结构的实现者。

use std::any::Any;
use serde_traitobject as s;

#[derive(Serialize, Deserialize, Debug)]
struct MyStruct {
    foo: String,
    bar: usize,
}

let my_struct = MyStruct {
    foo: String::from("abc"),
    bar: 123,
};

let erased: s::Box<dyn s::Any> = s::Box::new(my_struct);

let serialized = serde_json::to_string(&erased).unwrap();
let deserialized: s::Box<dyn s::Any> = serde_json::from_str(&serialized).unwrap();

let downcast: Box<MyStruct> = Box::<dyn Any>::downcast(deserialized.into_any()).unwrap();

println!("{:?}", downcast);
// MyStruct { foo: "abc", bar: 123 }

安全性

此crate通过使用relative::Vtable包装vtable指针,使其可以在进程间安全地传递。

这种方法目前还不能抵御恶意行为者。然而,如果我们假设非恶意行为者和典型的(静态或动态)链接条件,那么将其视为合理的也是可以的。

有关在rustc中进行的使这一方法安全的工作,请参阅此处

验证

为了验证目的,随vtable指针序列化了三件事情

  • 构建ID(128位);
  • 特质对象的type_id(64位);
  • 具体类型的type_id(64位)。

在Rust的未来某个时刻,我认为如果能够使用后者安全地查找和创建特质对象将非常棒。但目前,该功能尚不存在,因此这个crate所做的是序列化vtable指针(相对于静态基),并在它被使用和可能引发UB之前进行尽可能多的有效性检查。

前两项在使用vtable指针之前会进行有效性检查。构建ID确保vtable指针来自具有相同布局的二进制文件的调用1。类型ID确保正在反序列化的特质对象与序列化的特质对象相同类型。它们确保在非恶意条件下,尝试反序列化无效数据会返回错误而不是UB。具体类型的类型ID用作健康检查,如果它与要反序列化的具体类型的类型ID不同,则会导致panic。

关于冲突,128位的构建ID发生冲突的可能性极低,可以信赖它永远不会发生。64位的类型ID发生冲突是可能的,参见rust-lang/rust#10389,但在实践中发生的可能性极低。

vtable指针作为相对于此静态特质对象的usize进行(反)序列化。这使得它可以在典型的动态链接条件下工作,在这种情况下,相同的二进制文件的绝对vtable地址可能因调用而异,但相对地址保持不变。

总的来说,据我所知,存在三个安全漏洞。

  • 一个恶意用户拥有二进制文件的副本,可以轻易地构造一个通过验证的 build_idtype_id,从而控制跳转位置。
  • 序列化vtable指针的数据损坏,但不是用于验证的 build_idtype_id,导致跳转到任意地址。未来版本可以通过使用密码来修复,使得损坏只影响vtable指针的可能性极小,通过在序列化和反序列化时混合vtable指针和验证组件。
  • 动态链接条件,其中相对地址(vtable - 静态vtable)在不同二进制文件的调用之间不同。我确信这是可能的,但我没有遇到过这种情况,因此无法评论其普遍性。

1我认为这个要求并不严格必要,因为 type_id 应该包含所有可能影响安全性的信息(特质方法、调用约定等),但包含它是为了以防万一;提供更有用的错误信息;并降低冲突的可能性。

注意

该crate目前需要Rust nightly版。

许可协议

许可协议为以下之一

任选其一。

除非你明确表示,否则根据 Apache-2.0 许可协议定义的,你提交的任何旨在包含在作品中的贡献,都应如上双许可,不附加任何额外条款或条件。

依赖

~0.8–1.7MB
~31K SLoC