Cracking C++(13): 读取不超过n个字符

news2024/11/16 3:50:30

文章目录

    • 1. 目的
    • 2. 正确用法实例
    • 3. 纠正错误用法
      • 3.1 错误用法
      • 3.2 让 AddressSanitizer 告诉你错误
      • 3.3 解释
    • 4. 总结

1. 目的

在读取 pgm 格式图像的 meta 信息时, 使用了 %2s 这个格式串, 之前不是很了解, 尝试后发现, 如果不小心容易内存越界,而内存越界并不总是让运行的程序立即 crash, 会导致排查错误的成本升高。记录如下。

2. 正确用法实例

读取 .pgm 图像文件的 meta 信息时, 第一行是 P5 两个字符串。读取的代码为

char magic[3];
fscanf(fp, "%2s", magic);

意思是说从 文件句柄 fp 读取不超过2个字符, 存储到 magic 这个内存buffer中。

也可以是从控制台(stdin)读取输入:

char buf[3];
scanf("%2s", buf);

问: 明明是读取不超过 2 个字符, 为什么要申请3个字符呢?是否必要?
答: 必要的。
问: 那我偏要申请2个字符, 程序运行也没 crash 啊?
答: 内存越界并不总是立即 crash, 除非触发缺页中断。你可以开启 Address Sanitizer, 它会告诉你,你越界了。
问: 我不理解。fscanf 和 scanf 为啥要“多管闲事”? 那多出来的字符跟 fscanf 和 scanf 有啥关系?
答: 你打印下结果字符串就知道了。

3. 纠正错误用法

3.1 错误用法

// test.c
#include <stdio.h>

int ex1()
{
    char buf[2];  // 只申请了两个字符
    scanf("%2s", buf); // 读取不超过2个字符
    printf("buf is %s\n", buf); // 输出
    return 0;
}

int main()
{
    ex1();
    return 0;
}

编译和运行:

zz@Legion-R7000P% gcc test.c 
zz@Legion-R7000P% ./a.out 
he
buf is he

乍一看,程序运行良好,觉得“可以收工回家吃饭”了。

3.2 让 AddressSanitizer 告诉你错误

通常是搭配 -fsanitize=address -fno-omit-frame-pointer -g 编译选项使用,运行时第一次内存越界时,程序会终止, 并打印输出错误类型,错误原因。

此处使用 GCC, 你也可以使用 Visual Studio 或 XCode。 Visual Studio 需要 VS2019 >= 16.7 版本, 或 VS2022, 才支持 Address Sanitizer。

zz@Legion-R7000P% gcc test1.c -fsanitize=address -fno-omit-frame-pointer -g
zz@Legion-R7000P% ./a.out 
he
=================================================================
==162004==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffc96f1fd32 at pc 0x7fe14c1d786c bp 0x7ffc96f1fbb0 sp 0x7ffc96f1f338
WRITE of size 3 at 0x7ffc96f1fd32 thread T0
    #0 0x7fe14c1d786b in scanf_common ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors_format.inc:342
    #1 0x7fe14c1d84d3 in __interceptor___isoc99_vscanf ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:1530
    #2 0x7fe14c1d85e6 in __interceptor___isoc99_scanf ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:1551
    #3 0x55bca1d712ca in ex1 /home/zz/work/lenet_c/test1.c:7
    #4 0x55bca1d71354 in main /home/zz/work/lenet_c/test1.c:14
    #5 0x7fe14bf7cd8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #6 0x7fe14bf7ce3f in __libc_start_main_impl ../csu/libc-start.c:392
    #7 0x55bca1d71164 in _start (/home/zz/work/lenet_c/a.out+0x1164)

Address 0x7ffc96f1fd32 is located in stack of thread T0 at offset 34 in frame
    #0 0x55bca1d71238 in ex1 /home/zz/work/lenet_c/test1.c:5

  This frame has 1 object(s):
    [32, 34) 'buf' (line 6) <== Memory access at offset 34 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors_format.inc:342 in scanf_common
Shadow bytes around the buggy address:
  0x100012ddbf50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100012ddbf60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100012ddbf70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100012ddbf80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100012ddbf90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x100012ddbfa0: 00 00 f1 f1 f1 f1[02]f3 f3 f3 00 00 00 00 00 00
  0x100012ddbfb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100012ddbfc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100012ddbfd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100012ddbfe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100012ddbff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc
==162004==ABORTING

在这里插入图片描述

3.3 解释

scanf/fscanf 并不是“多管闲事”。 C 的字符串总是需要多分配一个字符, 如果 scanf/fscanf 简单粗暴的写入 n 个字符, 那么后续使用 strlen() 等函数时, 无法根据字符串结尾处的 \0 判断结束, 因为此时没有 \0 字符。

在执行 scanf()/fscanf() 的前、后, 分别打印 buf 字符串的内容, 可以发现读取2个字符后, 第3个字符会被自动设置为 0.

// test.c
#include <stdio.h>

int ex1()
{
    char buf[2];
    scanf("%2s", buf);
    printf("buf is %s\n", buf);
    return 0;
}

void print_buf(char* buf, int len)
{
    for (int i = 0; i < 3; i++)
    {
        printf("buf[%d] = %d\n", i, buf[i]);
    }
}

int ex2()
{
    char buf[3] = {1, 1, 1};
    print_buf(buf, 3);
    int n = scanf("%2s", buf);
    printf("n = %d\n", n);
    printf("-----\n");
    print_buf(buf, 3);
    return 0;
}

int main()
{
    //ex1();
    ex2();
    return 0;
}

运行如下

zz@Legion-R7000P% gcc test.c -fsanitize=address -fno-omit-frame-pointer -g 
zz@Legion-R7000P% ./a.out 
buf[0] = 1
buf[1] = 1
buf[2] = 1
he
n = 1
-----
buf[0] = 104
buf[1] = 101
buf[2] = 0

4. 总结

无论是 scanf() 还是 fscanf(), 都支持读取不超过 n 个字符, 通常目的就是读取 n 个字符, 这样就不用手写循环那么那麻烦了。

而读取不超过 n 个字符, 存储这 n 个字符的字符串, 需要 n+1 个字节的内存空间, 最后一个字符用于存储 \0

  • 如果内存空间等于 n 个字节, 虽然程序可能不会 crash, 但无法确保总是不 crash。换言之开发过程中总是应该开启 Address Sanitizer 来确保绝对安全正确。
  • 如果内存空间大于等于 n+1 个字符, 那么索引为 n 的字符将被 scanf()/fscanf() 填充为 \0
  • 正确用法, 再次举例: 读取(不超过)2个字符,代码为
    char buf[3];
    int n = scanf("%2s", buf);
    printf("buf = %s\n", buf);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/657126.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

花上半小时帮你快速熟悉微服务架构

本文将介绍微服务架构和相关的组件&#xff0c;介绍他们是什么以及为什么要使用微服务架构和这些组件。本文侧重于简明地表达微服务架构的全局图景&#xff0c;因此不会涉及具体如何使用组件等细节。 要理解微服务&#xff0c;首先要先理解不是微服务的那些。通常跟微服务相对…

读发布!设计与部署稳定的分布式系统(第2版)笔记02_停飞的代码异常

1. 以前“计划内的停机”很正常&#xff0c;现在则不被接受 2. 高可用性架构 2.1. CF系统不会遇到任何常见的单点失效问题 2.1.1. 硬件的每一部分都有冗余 2.1.1.1. CPU 2.1.1.2. 驱动器 2.1.1.3. 网卡 2.1.1.4. 电源 2.1.1.5. 网络交换机 2.1.1.6. 风扇 2.1.2. 为了…

Redis哨兵模式的配置

1.环境准备 master节点1个slave节点2个sentinel【哨兵】节点3个redis版本5.0.3操作系统&#xff1a;Centos7 2.主从节点配置 创建redis-conf目录&#xff0c;此目录用于存放主从节点的配置文件 复制redis.conf&#xff0c;然后创建三个配置文件&#xff1a;redis-6379.conf&…

循环缓冲题目

题目&#xff1a;一环形缓冲区由 6 个缓冲区 0~5 组成&#xff0c;其中 Full 表示装满数据的缓冲区&#xff0c;Empty 表示空缓冲区。按照顺时针方向&#xff0c;指针 Pf 指向第一个 “满” 缓冲区&#xff0c;指针 Pe 指向第一个 “空” 缓冲区。进程 In 在 Pe 指示下不断向 E…

XSS数据接收网站——XSS在线平台

文章目录 前言使用步骤1、进入到xss在线平台主页2、创建项目3、生成攻击poc4、查看返回结果 前言 平台的网址是&#xff1a; 链接: XSS在线平台 使用步骤 1、进入到xss在线平台主页 2、创建项目 我的项目&#xff0c;点击创建&#xff0c;项目名称和描述随便填&#xff0c;…

Docker安装和使用,Docker拉取Mysql.

Docker Unbuntu安装dockerdocker的相关操作开启docker服务查看镜像搜索镜像拉取镜像删除镜像运行容器查看容器停止运行容器重新运行容器删除容器构建一个Docker镜像登陆Dockerhub提交镜像到dockerhub退出dockerhub进入正在运行的容器的交互式终端其他docker操作 docker拉取mysq…

qemu arm Linux 环境测试交叉编译的 glib 库 测试用例 tests

环境搭建 ubuntu 20.04 arm 平台交叉编译 glib 库 交叉编译 glib 库 glib 库 本身带有大量的测试用例 tests&#xff0c;分别在 glib 各个模块目录下的 tests 目录&#xff0c;如果是 ARM Linux 平台的交叉编译&#xff0c;可以开启 installed_tests 选项 开启 glib tests 测…

2.pixi.js编写的塔防游戏(类似保卫萝卜)-场景编辑器

游戏说明 一个用pixi.js编写的h5塔防游戏&#xff0c;可以用electron打包为exe&#xff0c;支持移动端&#xff0c;也可以用webview控件打包为app在移动端使用 环境说明 cnpm6.2.0 npm6.14.13 node12.22.7 npminstall3.28.0 yarn1.22.10 npm config list electron_mirr…

一种正弦信号叠加高频噪声的信号基频率准确测量方法

1.问题 当信号叠加有高频噪声时&#xff0c;特别是类似有变频器这类强干扰源存在的情况下&#xff0c;如何测得信号的准确频率&#xff0c;是个问题。FFT要求长时间采样&#xff0c;对于嵌入式应用&#xff0c;采样点数和时间消耗都是个问题。而即使用示波器的波形叠加功能&…

Debian12 U盘安装

今天买了一个蓝牙适配器&#xff0c;想着在我的Centos7上把这个蓝牙使用起来&#xff0c;但遗憾的是即使经过淘宝客服的远程操作也无法正常使用起来&#xff0c;原因是我的Centos版本太低&#xff0c;有些头文件缺失内容导致编译不过&#xff0c;然后蓝牙驱动无法正常安装。在客…

【八】spring boot集成数据库连接池druid

spring boot集成数据库连接池druid 最近在进行程序优化的过程中发现程序瓶颈在数据库连接这块&#xff0c;于是开始研究怎么对数据库连接池参数进行调优&#xff0c;在这个过程中发现很多人使用druid很不规范&#xff0c;经常会出现导入的包和配置参数不对应的情况&#xff0c;…

Mybatis-Plus《学习笔记(22版尚硅谷)》

一、MyBatis-Plus 1.简介 MyBatis-Plus (opens new window)&#xff08;简称 MP&#xff09;是一个 MyBatis (opens new window)的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 我们的愿景是成为 MyBatis 最好的搭档&…

【SpringCloud入门】-- Ribbon入门

1.什么是Ribbon&#xff1f; Ribbon就是netflix公司的一个开源项目&#xff0c;主要功能是提供客户端负载均衡算法和服务调用。Ribbon客户端组件提供了完善的配置项&#xff0c;如连接超时&#xff0c;重试等等。Ribbon作为服务消费者的负载均衡器&#xff0c;有两种使用方式&…

LeetCode 2481. Minimum Cuts to Divide a Circle【数学,几何】简单

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…

AMD——CPU微架构分析

一、SoC架构 1.1 整体架构 Zeppelin 参考链接&#xff1a;wikichip: Zeppelin 通过infinity fabric总线将单die分成多die的SoC架构&#xff0c;每个Die包含两个CPU核&#xff08;CCX&#xff09;、2各DDR通道、USB、低功耗IO以及多个IFOP和IFIS serdes接口。 如下所述中&…

【Python 随练】三数字排序

题目&#xff1a; 输入三个整数 x, y, z&#xff0c;请按照从小到大的顺序输出这三个数。 简介&#xff1a; 在本篇博客中&#xff0c;我们将使用Python代码解决一个简单的排序问题&#xff1a;如何将输入的三个整数按照从小到大的顺序进行排序并输出。我们将提供问题的解析…

力扣动态规划专题(三)完全背包 518.零钱兑换II 377. 组合总和 Ⅳ 70. 爬楼梯 322. 零钱兑换 279.完全平方数 139.单词拆分 步骤及C++实现

文章目录 完全背包一维dp数组 滚动数组 518.零钱兑换II377. 组合总和 Ⅳ70. 爬楼梯322. 零钱兑换279.完全平方数139.单词拆分 完全背包 完全背包的物品数量是无限的&#xff0c;01背包的物品数量只有一个 完全背包和01背包分许步骤一样&#xff0c;唯一不同就是体现在遍历顺序上…

JVM内存分析:Aviator低版本内存泄漏问题分析

目录 1.频繁FullGC告警 2.堆转储操作生成dump文件 3.利用MAT工具分析dump文件 3.1 大对象视图分析内存泄漏原因 3.2 Aviator框架中什么地方用到ThreadLocal&#xff1f; 3.3 fnLocal为什么存在内存泄漏&#xff1f; 3.4 LambdaFunctionBootstrap为什么没有释放&#xff…

002mavenSettings配置

Maven –Settings 官网说明https://maven.apache.org/guides/mini/guide-mirror-settings.html 官网说明 模板 <?xml version"1.0" encoding"UTF-8"?> <settings xmlns"http://maven.apache.org/SETTINGS/1.0.0"xmlns:xsi"htt…

【Redis】持久化机制

一、三种持久化机制 RDF快照&#xff1a;通过某一时刻的快照&#xff0c;将数据以二进制形式永久存储在磁盘中。AOF&#xff1a;将每个修改命令通过write函数追加到文件中。混合持久化&#xff1a;AOF重写时&#xff0c;将重写这一刻之前的内存做RDB快照存储&#xff0c;并将此…