首页 > 其他分享 >如何在 Rust 中安全地处理 Openresty中的字符串?

如何在 Rust 中安全地处理 Openresty中的字符串?

时间:2024-07-10 11:46:29浏览次数:14  
标签:String 内存空间 ManuallyString 内存 Openresty 字符串 Rust

Hello World

Rust 以简洁高效安全而闻名,那么我们怎么集成到C 的项目中呢。尤其是字符串数据结构,该如何正确地交互。借此机会整理一下工作中遇到的难题,希望可以帮助大家走出坑。

我们先回顾一下 C 中字符串的结构。

在 C 语言中,字符是一个连续的内存地址空间以 \0 结尾。C 语言的字符串不会记录长度信息,而是遇到第一个\0 之前的地址空间作为长度信息。Rust 为了兼容C 的字符串,被称为 CStr。

但是在很多种语言中,如果遇到\0 作为结尾符,那么字符串中真的有\0 怎么办呢?所以 Nginx,lua 的字符串的数据结构,不光是一个连续的内存地址,而且还有字符串的长度信息。按照长度信息,从指针开始读取字符串就一定不会出错。Rust 中被称为 String。大概是这样

struct String {
    vec: Vec<u8>, // 存放字符串内容的内存空间
    len: usize, // 当前字符串长度
    capacity: usize, // 记录了当前 Vec 的容量,也就是可以存储的最大字节数。
}

C语言的字符串不支持动态扩容,每次扩容都需要自己申请一段新的内存,复制旧的内存到新的内存,并释放旧的内存。这个过程既繁琐,又容易出现内存问题。

Rust官方实现了这些功能,Rust 的字符串每次都已现有内存的 2 倍去扩容,并执行上述的操作。

在 Rust 中使用借用来表示 C 语言的字符串

Rust 的 String中的 vec 字段,指向传入的 C 语言内存地址即可。使用 Rust 的借用(Borrow)思想,可以很好地注明这类关系。但要注意的是,由于 Rust 的自动释放机制,在超出作用域后,Rust 会自动释放 String。而 Rust 无法正确的区分 String 中的 vec 是 C 语言管理的内存还是 Rust 管理的内存。这样就造成了著名的 double free 问题。所以我们要使用 ManuallyDrop 管理 Rust 中指向 C 语言的String。

在 Rust 中不要使用迭代器去遍历 Nginx/Lua 等字符串结构体

在 Rust 的 String 中,如果使用迭代器去遍历 Nginx的字符串,类似于下面的代码

while let Some(ch) = input_str.chars().nth(i){
	// todo!();
}

// or

for (i, b) in input_str.char_indices(){
	// todo!();
}

那么一定会出现问题的,下面我们来详细解释一下为什么。

在 Nginx ,或者 Openresty 中,存储字符串的时候不再以\0为结尾标志符。每个字符串都紧密相连。

但是在 Rust 中,String 的迭代器在遍历字符串的时候,依旧使用\0为结尾标志符,那么就会造成,迭代器会不停地访问内存,甚至越界访问到Nginx 中其他字符串

因为 Rust 的 String,在申请或扩容内存空间的时候,会使用\0初始化内存空间。Rust 的

String 只不过是 C Str 的加强版,并且 Rust 的 String 一定会小于 Capacity,String 中一定保证有\0

但是传入的 nginx 字符串却无法正常保证,为了安全考虑,请使用字符串的长度信息去便利字符串,我们可以改为

while i < input_str.len() {
    if let Some(ch) = input_str.chars().nth(i) {
	    // todo
    }
}

CStr转换为 Rust 的字符串的时候,为了性能考虑,禁用 utf8 检查

原因很简单,Rust 不会在意你的字符串是什么编码。所以你在转换的时候,如果使用 utf8 检查,Rust 只是遍历字符串并检查是否符合 utf8 标准。

但是,这里你一定要正确区分,传入的内容是字符串,还是二进制流。如不正常区分,则会出现如下错误

thread 'test_detect_2' panicked at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/str/mod.rs:666:13:
byte index 2 is not a char boundary; it is inside '\n' (bytes 1..2) of `
�H�

abc--`

正确地把 C字符串转换为 Rust String 的操作

为什么不转换为 Rust 的 Cstr?因为 CStr 其实还是 C 语言的表示,以\0为结尾符。那我们不如直接用 Rust 的 String,效果是一样的,并且还附带长度信息,在 WAF 中可以轻松应对 NULL 绕过攻击!

1. 阻止自动内存释放的 Rust 类型

/// 提供忽略自动 free 的字符串,防止 c 与 rust 在交互的时候,因为 rust 误拿到所有权而 free 内存,造成内存崩溃
#[derive(PartialEq, PartialOrd, Debug)]
pub struct ManuallyString {
    value: ManuallyDrop<String>,
}

为什么弄个这个结构体,而不是使用 mem::forget?为了方便,防止误操作而导致 double free。其二为了代码的整洁美观。

2. 使用 String::from_raw_part转换字符串

impl ManuallyString {
    pub fn new(data: *const c_char, len: usize) -> Self {
        ManuallyString {
            value: unsafe {
                ManuallyDrop::new(String::from_raw_parts(data as *mut u8, len, len))
            },
        }
    }
}

String::from_raw_parts 这个函数只是将 String 的 vec 内存空间设置为 C 的内存空间,并设置当前字符串长度信息

3. 使用字符串

impl Deref for ManuallyString {
    type Target = String;

    fn deref(&self) -> &String {
        &self.value
    }
}

我们为ManuallyString 实现 Deref trait 即可。这样很好的标识,字符串的所有权在当前ManuallyString中,使用者只是借用。超过作用于也不会触发 rust 的自动回收机制。

一句话概括,指向字符串的内存空间的所有权在 C 端,Rust只是借用!

graph LR subgraph C Memory C_Memory["C Memory Space(Owner)"] end subgraph Rust Manually["ManuallyString"] subgraph Borrow Borrow1["Borrow 1"] Borrow2["Borrow 2"] end end C_Memory --> Manually["Manually Struct"] Manually --> Borrow1 Manually --> Borrow2

标签:String,内存空间,ManuallyString,内存,Openresty,字符串,Rust
From: https://www.cnblogs.com/unicodeSec/p/18293583

相关文章

  • 代码随想录算法训练营第8天 | 复习字符串API、双指针
    2024年7月10日题344.翻转字符数组记得用双指针,时间复杂度最低。题541.反转字符串II首先自己实现一个String的reverse函数方便后面用,记得字符数组和字符串的互转方式。然后计算有多少组2k,分组处理即可。classSolution{publicStringreverseStr(Strings,intk){......
  • Java字符串(String、字符串拼接、原理)
    文章目录一、String字符串1.1创建方式【直接赋值、new一个对象】1.1.1使用字符串字面值直接赋值:(1)字符串字面量创建String对象的转换过程(2)一些方法(3)说明1.1.2使用`new`关键字创建字符串对象,将内容赋值给变量:(1)`String`类有多个构造函数,其中一些常用的包括:(2)说明(3)引用的......
  • 字符串函数
    在编写程序的过程中,我们经常需要用到字符串函数,为了方便对字符串进行处理,C语言标准库中提供了一系列库函数。在使用标准库中的字符串函数之前,我们需要引用头文件string.h。接下来我们对经常用到的字符串函数进行逐一介绍。strlen的使用和模拟实现strlen的全称是stringlengt......
  • C#字符串操作:判断一个字符串是否存在于另一个字符串按特定字符分割后的子字符串中的几
    要判断一个字符串是否存在于另一个字符串按特定字符分割后的子字符串中,可以使用以下几种方法:方法一:使用Split和Array.Exists你可以使用Split方法将字符串分割成子字符串数组,然后使用Exists方法检查目标字符串是否在数组中:usingSystem;classProgram{staticvoid......
  • 详解 | 什么是GeoTrust
    GeoTrust是一家全球知名的数字证书颁发机构(CertificateAuthority,简称CA),专注于提供SSL/TLS证书和其他相关的网络安全产品。1、历史背景:GeoTrust成立于2001年,最初作为一个独立的公司运营。2006年,GeoTrust被VeriSign收购。后来,在2010年,VeriSign的SSL业务又被Symantec收购。而现......
  • Day 6 翻转字符串和整数反转
    给自己放了一周假感觉过了很长时间,说来有点惭愧,是时候继续学习下去了。还是继续按照每天两道题的速度将题目做下去。之前将初级算法电子书的数组部分题目做完,但是还没来得及复习,今天继续向后做字符串的题目。翻转字符串第一个题目是翻转字符串,是第344题,就是将字符串倒过来,要......
  • 编写一函数,由实参传来一个字符串,统计此字符串中字母、数字、空格和其它字符的个数, 在
    /编写一函数,由实参传来一个字符串,统计此字符串中字母、数字、空格和其它字符的个数,在主函数中输入字符串以及输出上述结果。只要结果,别输出什么提示信息。/#include<stdio.h>#include<string.h>#include<ctype.h>intfun(char*buff){intsum=0;while(*buff......
  • 解码Python字符串:拯救失落的字符世界!
    一、字符串的定义在Python中,字符串可以用单引号('')或双引号("")括起来定义。例如:s='HelloWorld!'print(s)输出:HelloWorld!你还可以使用三重引号('''或""")来定义多行字符串。但大多数时候三重引号用来注释文档例如:s='''HelloWorld!''&#......
  • 【算法篇】KMP算法,一种高效的字符串匹配算法
    我们今天了解一个字符串匹配算法-KMP算法,内容难度相对来说较高,建议先收藏再细品!!!KMP算法的基本概念KMP算法是一种高效的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。该算法的主要使用场景就是在字符串(也叫主......
  • rust线程池
    #![allow(unused)]usestd::sync::{mpsc,Arc,Mutex};usestd::thread;//定义消息类型,可以是新任务或终止信号enumMessage{NewJob(Job),Terminate,}//定义线程池结构体pubstructThreadPool{workers:Vec<Worker>,//sender:mpsc::Sender<J......