惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

G
Google Developers Blog
S
Schneier on Security
The Hacker News
The Hacker News
P
Proofpoint News Feed
Spread Privacy
Spread Privacy
L
LINUX DO - 热门话题
L
Lohrmann on Cybersecurity
I
Intezer
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Schneier on Security
Schneier on Security
Security Latest
Security Latest
AWS News Blog
AWS News Blog
B
Blog RSS Feed
Microsoft Security Blog
Microsoft Security Blog
有赞技术团队
有赞技术团队
博客园 - 叶小钗
The Last Watchdog
The Last Watchdog
O
OpenAI News
月光博客
月光博客
Hacker News: Ask HN
Hacker News: Ask HN
阮一峰的网络日志
阮一峰的网络日志
S
Security @ Cisco Blogs
Google Online Security Blog
Google Online Security Blog
云风的 BLOG
云风的 BLOG
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Latest news
Latest news
P
Palo Alto Networks Blog
Last Week in AI
Last Week in AI
M
MIT News - Artificial intelligence
Google DeepMind News
Google DeepMind News
P
Proofpoint News Feed
C
CERT Recently Published Vulnerability Notes
Apple Machine Learning Research
Apple Machine Learning Research
U
Unit 42
PCI Perspectives
PCI Perspectives
博客园 - 聂微东
SecWiki News
SecWiki News
宝玉的分享
宝玉的分享
Forbes - Security
Forbes - Security
H
Heimdal Security Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Hugging Face - Blog
Hugging Face - Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Troy Hunt's Blog
博客园 - 三生石上(FineUI控件)
Application and Cybersecurity Blog
Application and Cybersecurity Blog
罗磊的独立博客
WordPress大学
WordPress大学
D
Darknet – Hacking Tools, Hacker News & Cyber Security

文章列表 - 她和她的猫

使用小鹤双拼一个月 折腾 Hugo PaperMod 主题 去长鹿旅游休博园 1Password 自动化登录堡垒机 深入 Hyperf:Inject 注解是如何工作的? 译|理解容器镜像层 使用 Rust 实现高性能预写日志(Write Ahead Log) 译|使用 Linux 命名空间隔离系统 深入 Hyperf:HTTP 服务是如何处理请求的? Linux 文件权限学习笔记 译|了解 Bitcask:基于日志结构的 KV 存储引擎 还原 SM2 压缩公钥的几种方法 深入 Hyperf:HTTP 服务启动时发生了什么? 一次 Hyperf 注解失效问题分析 将博客迁移到又拍云 译|PHP 7 新的 Hashtable 实现 排查 ES 查询问题:深入了解 json_encode() 函数 浅析 Redlock 分布式锁实现原理 Yar 源码阅读笔记:RPC 服务端 Yar 源码阅读笔记:客户端的并行调用 Yar 源码阅读笔记:客户端的同步调用 Yar 源码阅读笔记:数据传输模块 Yar 源码阅读笔记:消息编码模块 Yar 源码阅读笔记:RPC 通信协议 Yar 源码阅读笔记:开篇 PHP 8 新特性介绍 基于 OpenSSL 实现国密 SM4 加解密 PHP 源码阅读笔记:编译与调试 PHP 什么是惊群问题 大端模式和小端模式 基于 GitHub Actions 定时推送网址到百度站长平台 聊聊五种 I/O 模型 Workerman 源码分析:文件上传 浅入浅出 HTTP 协议 PHP 网络编程:构建 MySQL 蜜罐获取攻击者微信 ID 使用 Workerman 接入 Bilibili 直播弹幕协议 使用 GitHub Actions 自动部署 Hexo 【转载】PHP 程序员进阶之路 优化 Workerman 检查主进程是否存活的逻辑 基于 Redis 实现分布式锁 《PHP 实现 Base64 编码/解码》笔记 PHP 多进程下载必应壁纸 基于 Redis 实现延迟队列 【转载】PHP 实现 Base64 编码/解码 使用 MySQL FIELD() 函数自定义排序规则 Laravel 的 Facades 实现原理 C语言单链表实现约瑟夫环 数据结构学习笔记:顺序栈和链栈(C语言) 使用 QueryList + Redis 下载壁纸 在 Laravel 登录/注册中使用 mews/captcha 扩展包 使用 mews/captcha 扩展包为 Laravel 应用添加图片验证码 给 YOURLS 短网址系统编写插件《批量生成短网址》 给 YOURLS 短网址系统编写插件《Hello World!》 PHP 7.1 使用 json_encode 函数造成浮点类型数据出现精度问题 Laravel 授权策略(Policy)的基本使用 Ubuntu 系统下 WiFi 频繁掉线解决方法 Laravel 应用部署到 Nginx 服务器上的第一个坑 Ubuntu 16.04 LST 安装 Redis 和 Composer PHP 采集逐浪小说章节列表 Nginx 环境 SSL 强制 HTTP 301 跳转到 HTTPS 【工作&生活】第一周 ServiceStack.Redis 与多线程 Count the number of Duplicates C# Base64 加密及解密 ASP.NET MVC 使用动态类型传递数据 Django 使用 order_by() 对数据进行排序操作 ASP.NET + jQuery + AJAX 实现用户登录 ASP.NET 从客户端中检测到有潜在危险的 Request.Form 值 ASP.NET 使用 FormData 实现 AJAX 上传图片 Python 使用 BeautifulSoup 抓取网页 贪心算法之字符串的完美度 一言不和就造轮子之 CookieHelper ASP.NET 上传图片 DBHelper 类 C# 捕获屏幕源码 用 JavaScript 做一些有趣的事情 第一篇文章 好久不见。 如何防止非法调用 PHP 文件 清明小记。 PHP 生成随机字符串 Z-Blog PHP 程序做 301 跳转 PHP 处理数组常用的几个函数 Sublime Text 3 快捷键总结 PHP 使用 file_get_contents() 函数实现采集网页 使用 C# 编写学生信息管理系统 终于到了。 zblog报错: magic_quotes_gpc 和 safe_mode 世事无常。 如何使用 PHP 上传文件 安卓手机编写和运行 PHP 程序的软件 使用 AJAX 刷新、验证 PHP 图片验证码 PHP 生成图片验证码 PHP 之 session 的使用方法 PHP 之 cookie 的使用方法 PHP 实现分页原理详解 Thinkphp 框架中系统常量在 Js 文件中不解析的解决方法
什么是二进制安全
她和她的猫 · 2021-03-25 · via 文章列表 - 她和她的猫

众所周知,C 语言中使用字符数组来表示字符串,并在字符串末尾使用空字符 \0 标识字符串结束。

如果字符串中包含 \0 或者二进制数据,就会导致 strlen 函数获取的长度跟字符串实际的长度不一致。

int main()
{
    char *str1 = "hello her-cat";
    char *str2 = "hello\0her-cat";
    char *str3 = "hello\x00her-cat";

    printf("str1: %lu\n", strlen(str1));
    printf("str2: %lu\n", strlen(str2));
    printf("str3: %lu\n", strlen(str3));

    return 1;
}

// 输出:
// str1: 13
// str2: 5
// str3: 5

可以看到 str2 和 str3 都只统计了 hello 的长度,为什么会出现这种情况?

因为 strlen 函数是通过遍历字符串来计算长度的,时间复杂度为 O(n)。在遍历的过程中,如果某个字符等于 \0, 就会停止遍历并返回第一个字符到该字符的字符数量,也就是字符串的长度。

当字符串中包含 \0 就会导致提前停止遍历,导致得到错误的字符串长度。所以我们称 C 语言中的 strlen 函数是非二进制安全的。

PHP 的 strlen 函数是二进制安全的,因为它不依赖于 \0 确定字符串的长度。

<?php

$str1 = "hello her-cat";
$str2 = "hello\0her-cat";
$str3 = "hello\x00her-cat";

printf("str1: %lu\n", strlen($str1));
printf("str2: %lu\n", strlen($str1));
printf("str3: %lu\n", strlen($str1));

// 输出:
// str1: 13
// str2: 13
// str3: 13

在 PHP 字符串结构体中,使用了 len 字段保存字符串的长度,调用 strlen 时读取该值就能得到字符串的长度。

struct _zend_string {
	zend_refcounted_h gc; // 垃圾回收信息
	zend_ulong        h; // hash 值
	size_t            len; // 字符串长度
	char              val[1]; // 柔性字节数组,保存字符串
};

// 读取字符串长度的宏
#define ZSTR_LEN(zstr)  (zstr)->len

ZEND_FUNCTION(strlen)
{
	zend_string *s;

	// 解析 strlen 的参数 s
	// 1, 1 表示最小参数个数和最大参数个数
	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_STR(s)
	ZEND_PARSE_PARAMETERS_END();

	// 返回字符串长度
	RETVAL_LONG(ZSTR_LEN(s));
}

《Redis 设计与实现》中解释了什么是二进制安全:

SDS 的 API 都是二进制安全的(binary-safe): 所有 SDS API 都会以处理二进制的方式来处理 SDS 存放在 buf 数组里的数据,程序不会对其中的数据做任何限制、过滤、或者假设 —— 数据在写入时是什么样的, 它被读取时就是什么样

为什么 C 语言的 strlen 函数不是二进制安全的?

因为它依赖字符串中的数据,对字符串中的数据做了过滤,只要字符串出现 \0 就认为字符串结束,导致计算长度时读取到的数据跟写入的数据不一致。

那么 Redis 是怎么解决二进制安全的问题呢?

在书中也有提到,由于直接使用 C 字符串存在二进制安全问题,所以不能用 C 字符串保存二进制数据(文本、图片)。

Redis 作为一个 KV 数据库,肯定不能限制用户存取的数据的类型,为了解决这个问题,Redis 设计了 SDS(Simple Dynamic String) 数据结构,又称简单动态字符串,结构体如下:

struct sdshdr {
    // 记录 buf 数组中已使用字节的数量
    // 等于 SDS 所保存字符串的长度
    int len;

    // 记录 buf 数组中未使用字节的数量
    int free;

    // 字节数组,用于保存字符串
    char buf[];
};

这里用的是 Redis 3.0 版本的 SDS,新版本的 Redis 对 SDS 进行了优化,结构体也有变化。

与 PHP 处理的方式相同,也是通过 len 的字段用来保存字符串的长度,不仅避免了二进制安全问题,同时提高了获取字符串长度的效率,不需要每次都去遍历 buf 字节数组。

同时,SDS 相对于 C 字符串具有以下优点:

  • 常数复杂度获取字符串长度
  • 杜绝缓冲区溢出
  • 减少修改字符串时带来的内存重分配次数
  • 二进制安全
  • 兼容部分 C 字符串函数

关于 SDS 完整的实现可以查看 SDS 与 C 字符串的区别

上面例子都是关于字符串的,这里再写一个二进制数据的例子。

首先用 PHP 生成二进制数据写入到文件中,并打印出来。

<?php

$str = sprintf('%s%s%s', pack('N', 123), pack('n', 456), pack('n', 789));

file_put_contents('data.dat', $str);

var_dump($str);

// 输出:
// string(8) "{�"

因为 PHP 对字符串读写是二进制安全的,所以能够正确打输出长度和内容。

接下来用 C 语言读取 data.dat 文件并输出。

int main()
{
    FILE *fp;
    char str[8];

    fp = fopen("data.dat", "r");
    fread(&str, 8, 8, fp);

    printf("len: %lu\n", strlen(str));
    printf("str: %s\n", str);

    return 1;
}

// 输出:
// len: 0
// str:

意料之中的长度为0、内容为空,用 GDB 调试可以看到 str 其实是有内容的。

(gdb) b main
Breakpoint 1 at 0x792: file /tmp/tmp.8inXlhJrYC/main.c, line 5.
(gdb) r
Starting program: /home/vagrant/code/her-cat/binary-safe/build/bin/main

Breakpoint 1, main () at /tmp/tmp.8inXlhJrYC/main.c:5
5	{
(gdb) u 12
main () at /tmp/tmp.8inXlhJrYC/main.c:12
12	    printf("len: %lu\n", strlen(str));
(gdb) p str
$1 = "\000\000\000{\001\310\003\025"

最后总结一下,二进制安全就是:严格按照二进制的方式进行读写数据,不关心数据的内容,写入是什么样,读取就是什么样。

如果不能做到这些,那就是非二进制安全的。

一个没有感情的二进制数据读写工具。