微软蓝屏事件揭秘:有问题的数据引发内存读取越界
讲动人的故事,写懂人的代码
CrowdStrike前一阵在官网上发布了上周爆发的全球企业微软蓝屏事件的官方初步复盘结果。其中谈到了这次事件的根本原因:
2024年7月19日,我们部署了两个额外的IPC模板实例。由于内容验证器中的一个bug,使得即使其中一个模板实例存在有问题的内容数据,但这个模板实例也通过了验证。
基于模板类型在初次部署前(2024年3月5日)执行过测试,我们对内容验证器中执行的检查采取信任态度,以及之前成功的IPC模板实例部署,所以这些模板实例被部署到生产环境中。
当传感器接收到并加载到内容解释器时,通道文件291中的有问题内容,导致内存读取越界,从而触发异常。代码无法优雅地处理这个异常,导致Windows操作系统崩溃(蓝屏)。
用大白话说,根本原因是内容验证器由于有bug,所以没有查出模板实例中有问题的内容数据。结果这些内容数据有问题的模板实例,上了生产环境。之后,生产环境的传感器中的内容解释器,在读取这些内容数据有问题的模板实例时,导致内存读取越界。而用C++编写的内容解释器,无法优雅地处理这个“内存读取越界”的异常情况,从而导致全球范围的企业微软蓝屏事件。
什么是“内存读取越界”?
内存读取越界是指程序试图访问或读取超出其被分配或合法范围的内存区域的行为。这种情况通常发生在数组或其他连续内存结构中,当索引或指针超出了有效的边界时就会出现。具体来说,内存读取越界的本质,就是访问未经授权或未分配的内存位置。
内存读取越界常见的场景,包括数组索引超出其定义的大小,指针引用越过分配的内存块边界,以及字符串操作超出字符串的实际长度。
内存读取越界的潜在后果,包括读取未初始化或无关的数据,程序不稳定或崩溃,以及可能导致信息泄露或被攻击者利用这样的安全漏洞。
一些编程语言(如C/C++)可能允许这种行为而不立即报错。其他语言(如Java、Python、Rust)通常会抛出异常或导致程序立即终止。
检测和预防内存读取越界的方法,包括使用支持边界检查的数据结构和函数,采用静态代码分析工具,以及在编码时注意边界条件的处理。
C++真的允许内存读取越界这种行为而不立即报错吗?
是的。不信的话,可以把下面的C++代码,复制粘贴到repl.com页面上运行,看看运行结果。(注意,下面的C++代码只是为了说明内存读取越界问题,而模拟了数组索引超出其定义的大小的内存读取越界场景。这并不是这次事件真正出问题的代码哦。)
1 #include <iostream>2 #include <stdexcept>3 #include <vector>4 5 // 模拟从传感器接收数据的函数6 std::vector<int> receiveSensorData(int channel) {7 // 假设Channel 291的数据包含问题内容8 if (channel == 291) {9 return {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // 问题数据
10 } else {
11 return {6, 7, 8, 9, 10}; // 正常数据
12 }
13 }
14
15 // 模拟内容解释器类
16 class ContentInterpreter {
17 public:
18 void loadContent(const std::vector<int> &data) {
19 if (data.size() < 10) {
20 throw std::runtime_error("Data size too small for processing");
21 }
22
23 // 模拟处理数据
24 for (size_t i = 0; i <= data.size(); ++i) {
25 // 越界访问,最后一次循环会导致越界
26 std::cout << "Processing data: " << data[i] << std::endl;
27 }
28 }
29 };
30
31 int main() {
32 int channel = 291; // 指定故障发生的通道
33 try {
34 std::vector<int> sensorData = receiveSensorData(channel);
35 ContentInterpreter interpreter;
36 interpreter.loadContent(sensorData);
37 } catch (const std::exception &e) {
38 std::cerr << "Exception caught: " << e.what() << std::endl;
39 }
40
41 return 0;
42 }
// 运行结果:
// Processing data: 1
// Processing data: 2
// Processing data: 3
// Processing data: 4
// Processing data: 5
// Processing data: 6
// Processing data: 7
// Processing data: 8
// Processing data: 9
// Processing data: 10
// Processing data: 1041
注意,上面代码第24行,i <= data.size();
就出现了越界。正确代码应该是i < data.size();
。结果一运行,C++代码并没有在内存读取越界后立即中止,而是继续执行,打印出Processing data: 1041。这个1041就是内存读取越界后获得的越界数据。
这个越界数据1041看起来貌似人畜无害,但这种运行时遇到内存读取越界还继续执行的行为,确实带来了下面更大的风险。
- 安全风险:读取未定义的内存区域可能导致敏感信息泄露。
- 稳定性问题:程序可能在之后的某个时刻因为这个未检测到的错误而崩溃,就像这次微软蓝屏那样。
- 调试困难:因为错误没有在发生点被捕获,可能导致问题源头难以定位。
如果把上面的C++代码,转换成等效的Rust代码,运行后会怎样?
1 use std::fmt;2 use std::vec::Vec;3 4 #[derive(Debug)]5 struct DataSizeTooSmallError;6 7 impl fmt::Display for DataSizeTooSmallError {8 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {9 write!(f, "Data size too small for processing")
10 }
11 }
12
13 impl std::error::Error for DataSizeTooSmallError {}
14
15 fn receive_sensor_data(channel: i32) -> Vec<i32> {
16 if channel == 291 {
17 vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10] // 问题数据
18 } else {
19 vec![6, 7, 8, 9, 10] // 正常数据
20 }
21 }
22
23 struct ContentInterpreter;
24
25 impl ContentInterpreter {
26 fn load_content(&self, data: &[i32]) -> Result<(), Box<dyn std::error::Error>> {
27 if data.len() < 10 {
28 return Err(Box::new(DataSizeTooSmallError));
29 }
30
31 // 模拟处理数据
32 for i in 0..=data.len() {
33 // 越界访问,最后一次循环会导致越界
34 println!("Processing data: {}", data[i]);
35 }
36 Ok(())
37 }
38 }
39
40 fn main() {
41 let channel = 291; // 指定故障发生的通道
42 let sensor_data = receive_sensor_data(channel);
43 let interpreter = ContentInterpreter;
44
45 match interpreter.load_content(&sensor_data) {
46 Ok(_) => (),
47 Err(e) => println!("Exception caught: {}", e),
48 }
49 }
// 运行结果
// Processing data: 1
// Processing data: 2
// Processing data: 3
// Processing data: 4
// Processing data: 5
// Processing data: 6
// Processing data: 7
// Processing data: 8
// Processing data: 9
// Processing data: 10
// thread 'main' panicked at src/main.rs:34:45:
// index out of bounds: the len is 10 but the index is 10
// note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
从上面代码可以看到,第32行虽然发生了内存读取越界(正确代码应该是for i in 0..data.len()
),但Rust代码在尝试访问越界内存时立即panic并中止了程序执行。输出清楚地指出了错误的位置和原因:“index out of bounds: the len is 10 but the index is 10”。这种立即中止程序执行行为,因为下面的原因而更安全。
- 立即发现问题:错误在发生点被捕获,便于定位和修复。
- 防止未定义行为:阻止了潜在的内存损坏或信息泄露。
- 提高可靠性:通过快速失败,避免了程序在损坏状态下继续运行。
如果把上面的C++代码,转换成等效的Java代码,运行后会怎样?
1 import java.util.List;2 import java.util.ArrayList;3 4 public class Main {5 // 模拟从传感器接收数据的函数6 public static List<Integer> receiveSensorData(int channel) {7 List<Integer> data = new ArrayList<>();8 if (channel == 291) {9 // 问题数据
10 data.add(1);
11 data.add(2);
12 data.add(3);
13 data.add(4);
14 data.add(5);
15 data.add(6);
16 data.add(7);
17 data.add(8);
18 data.add(9);
19 data.add(10);
20 } else {
21 // 正常数据
22 data.add(6);
23 data.add(7);
24 data.add(8);
25 data.add(9);
26 data.add(10);
27 }
28 return data;
29 }
30
31 // 模拟内容解释器类
32 static class ContentInterpreter {
33 public void loadContent(List<Integer> data) throws Exception {
34 if (data.size() < 10) {
35 throw new Exception("Data size too small for processing");
36 }
37
38 // 模拟处理数据
39 for (int i = 0; i <= data.size(); i++) {
40 // 越界访问,最后一次循环会导致越界
41 System.out.println("Processing data: " + data.get(i));
42 }
43 }
44 }
45
46 public static void main(String[] args) {
47 int channel = 291; // 指定故障发生的通道
48 try {
49 List<Integer> sensorData = receiveSensorData(channel);
50 ContentInterpreter interpreter = new ContentInterpreter();
51 interpreter.loadContent(sensorData);
52 } catch (Exception e) {
53 System.err.println("Exception caught: " + e.getMessage());
54 }
55 }
56 }
// 运行结果:
// Processing data: 1
// Processing data: 2
// Processing data: 3
// Processing data: 4
// Processing data: 5
// Processing data: 6
// Processing data: 7
// Processing data: 8
// Processing data: 9
// Processing data: 10
// Exception caught: Index 10 out of bounds for length 10
从上面代码可以看到,第39行虽然发生了内存读取越界(正确代码应该是i < data.size();
),但Java的表现类似于Rust,在尝试访问越界元素时抛出了IndexOutOfBoundsException
异常。这个异常被main
方法中的try-catch
块捕获并打印。这种方式也提供了下面良好的安全性。
- 异常处理:提供了结构化的错误处理机制。
- 错误信息明确:异常信息清楚地指出了问题所在。
- 防止继续执行:阻止了在错误状态下继续执行,提高了程序的可靠性。
专业的C++程序员会如何改进代码以避免内存读取越界问题?
专业的C++程序员,会采用以下方式来改进代码,以优雅地处理潜在的越界行为。这种方法注重预防、使用现代C++特性,并在适当的层次处理异常。
1 #include <algorithm>2 #include <iostream>3 #include <stdexcept>4 #include <vector>5 6 // 模拟从传感器接收数据的函数7 std::vector<int> receiveSensorData(int channel) {8 if (channel == 291) {9 return {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // 问题数据
10 } else {
11 return {6, 7, 8, 9, 10}; // 正常数据
12 }
13 }
14
15 // 模拟内容解释器类
16 class ContentInterpreter {
17 public:
18 void loadContent(const std::vector<int> &data) {
19 if (data.size() < 10) {
20 throw std::runtime_error("Data size too small for processing");
21 }
22
23 // 使用基于范围的for循环,避免手动索引
24 for (const auto &value : data) {
25 processData(value);
26 }
27 }
28
29 private:
30 void processData(int value) {
31 std::cout << "Processing data: " << value << std::endl;
32 }
33 };
34
35 // 错误处理函数
36 void handleError(const std::exception &e) {
37 std::cerr << "Error: " << e.what() << std::endl;
38 // 在这里可以添加日志记录、错误报告等逻辑
39 }
40
41 int main() {
42 int channel = 291; // 指定故障发生的通道
43
44 try {
45 std::vector<int> sensorData = receiveSensorData(channel);
46 ContentInterpreter interpreter;
47 interpreter.loadContent(sensorData);
48 } catch (const std::exception &e) {
49 handleError(e);
50 return 1; // 非正常退出
51 }
52
53 return 0;
54 }
// 运行结果:
// Processing data: 1
// Processing data: 2
// Processing data: 3
// Processing data: 4
// Processing data: 5
// Processing data: 6
// Processing data: 7
// Processing data: 8
// Processing data: 9
// Processing data: 10
这一版的C++代码主要包括以下改进。
- 第24行,使用基于范围的for循环:
在loadContent
方法中,使用基于范围的for
循环替代了手动索引。这完全消除了越界访问的可能性。 - 错误处理策略:
第36行,创建了一个单独的handleError
函数来集中处理错误。这使得错误处理逻辑更加集中和一致。 - 异常处理:
在main
函数中捕获所有可能的异常,并使用handleError
函数处理它们。这提供了一个统一的错误处理机制。 - 返回错误代码:
第50行,在发生错误时,程序返回非零值,表示非正常退出。 - 模块化:
第30行,将数据处理逻辑封装在processData
私有方法中,提高了代码的模块性和可维护性。
专业的Rust程序员会如何改进代码以避免内存读取越界问题?
类似地,专业Rust程序员,通常会使用以下方法来改进这段代码,避免内存读取越界的问题。即使用 for
循环与迭代器,而不是索引访问。这是最常用且最安全的方法,因为它完全避免了手动索引,从而消除了越界访问的可能性。
// 使用 for 循环遍历数据for &item in data {println!("Processing data: {}", item);}
专业的Java程序员会如何改进代码以避免内存读取越界问题?
专业的Java程序员通常会使用以下方法来改进这段代码,避免内存读取越界的问题。
使用增强型for循环(for-each循环)或者Java 8引入的流式API。
这些都是最常用且最安全的方法,因为它完全避免了手动索引,从而消除了越界访问的可能性。修改后的loadContent
方法如下所示:
public void loadContent(List<Integer> data) throws Exception {if (data.size() < 10) {throw new Exception("Data size too small for processing");}// 使用增强型for循环遍历数据for (Integer item : data) {System.out.println("Processing data: " + item);}
}
或者使用Java 8的流式API:
public void loadContent(List<Integer> data) throws Exception {if (data.size() < 10) {throw new Exception("Data size too small for processing");}// 使用流式API遍历数据data.forEach(item -> System.out.println("Processing data: " + item));
}
这些方法有以下优点。
- 安全性:完全消除了越界访问的可能性。
- 可读性:代码更加简洁清晰,意图更加明确。
- 性能:在大多数情况下,这种方法的性能与传统的索引遍历相当。
此外,这些方法还符合Java的现代编程风格,更加符合函数式编程的思想。
如果喜欢这篇文章,别忘了给文章点个“赞”,好鼓励我继续写哦~😃
相关文章:

微软蓝屏事件揭秘:有问题的数据引发内存读取越界
讲动人的故事,写懂人的代码 CrowdStrike前一阵在官网上发布了上周爆发的全球企业微软蓝屏事件的官方初步复盘结果。其中谈到了这次事件的根本原因: 2024年7月19日,我们部署了两个额外的IPC模板实例。由于内容验证器中的一个bug,使…...

NASA:北极ARCTAS差分吸收激光雷达(DIAL)遥感数据
ARCTAS Differential Absorption Lidar (DIAL) Remotely Sensed Data ARCTAS差分吸收激光雷达(DIAL)遥感数据 简介 ARCTAS差分吸收激光雷达(DIAL)遥感数据是一种远程感测技术,用于测量大气中不同波长的激光辐射被大…...

Android 文件上传与下载
在实际开发涉及文件上传不会自己写上传代码,一般 会集成第三网络库来做图片上传,比如android-async-http,okhttp等,另外还有七牛也提供 了下载和上传的API。 1.项目用到的图片上传的关键方法: 这里用到一个第三方的库…...

Java语言的充电桩系统Charging station system
介绍 SpringBoot 框架,充电桩平台充电桩系统充电平台充电桩互联互通协议云快充协议1.5-1.6协议新能源汽车二轮车公交车二轮车充电-四轮车充电充电源代码充电平台源码Java源码-共享充电桩-充电桩软件 软件介绍 小程序端:城市切换、附近电站、电桩详情页…...

RCE之无参数读取文件
什么是无参数? 顾名思义,就是只使用函数,且函数不能带有参数,这里有种种限制:比如我们选择的函数必须能接受其括号内函数的返回值;使用的函数规定必须参数为空或者为一个参数等 例题: <?…...

Python GUI开发必看:Tkinter Button控件使用详解
Button(按钮)组件用于实现各种各样的按钮。 Button组件可以包含文本或图像,你可以将一个Python的函数或方法与之相关联,当按钮被按下时,对应的函数或方法将被自动执行。 Button组件仅能显示单一字体的文本,…...
上海市计算机学会竞赛平台2024年7月月赛丙组得分排名
题目描述 给定 nn 名学生的考试得分,这些学生的学号为 11 到 nn,其第 ii 号学生的得分为 aiai,请将这些学生按照分数从大到小的顺序排列并输出学号序列。 若两个学生得分相同,则先输出较小的学号。 输入格式 第一行…...

Can GPT-3 Perform Statutory Reasoning?
文章目录 题目摘要相关工作SARAGPT-3 对美国法典的了解GPT-3 在对合成法规进行简单推理时遇到困难结论 题目 GPT-3 可以进行法定推理吗? 论文地址:https://arxiv.org/abs/2302.06100 摘要 法定推理是用事实和法规进行推理的任务,法规是立法机…...

redis面试(十一)锁超时
boolean res lock.tryLock(100, 10, TimeUnit.SECONDS); RedissonLock里面有这样一个方法tryLock(),意思是尝试获取锁的结果。 最大等待时间100s,并且获取到锁之后,10s之内没有释放的话,锁会自动失效。 尝试获取锁超时 time …...

C代码做底层及Matlab_SimuLink做应用层设计单片机程序
前言:SimuLink工具极其强大,但是能直接支持单片机自主开发的很少,造成这个问题的原因主要是我们使用的芯片底层多是C代码工程,芯片厂家也只提供C代码库,很少能提供SimuLink的支持库,即使提供也不是很不完善,如NXP的一些芯片提供的SimuLink库不含盖高级应用,再比如意法半…...
Cloud Kernel SIG 月度动态:ANCK OOT 驱动基线更新,发布 2 个 ANCK 版本
Cloud Kernel SIG(Special Interest Group):支撑龙蜥内核版本的研发、发布和服务,提供生产可用的高性价比内核产品。 01 SIG 整体进展 1. 发布 ANCK 5.10-016.4 小版本。 2. 发布 ANCK 5.10-017.1 小版本。 3. ANCK 新增海光平…...

vue3仿飞书头像,根据不同名称生成不同的头像背景色
效果展示: 传递三个参数: name:要显示的名称;size:头像的大小;cutNum:分割当前名称的最后几位数; 代码如下: <template><div:style"{color: #fff,borde…...
SpringBoot整合三方
SpringBoot整合redis 引入redis依赖包 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>redis.clients</groupId&g…...

React之组件的使用
Vue、React和Angular是三个流行的前端框架,采用组件化的开发方式。支持虚拟DOM(Virtual DOM)技术,有丰富的生态系统、大量的插件和工具可以使用。Vue的语法是传统的HTML和JavaScript,React使用JSX语法,Angu…...
深度学习--长短期记忆网络
1.引入 RNN 可以将以前的信息与当前的信息进行连接。例如,在视频中,可以用前面的帧来 帮助理解当前帧的内容;在文本中,可以用前面半句话的内容来预测后面的内容。但是, RNN 存在一个记忆消失的问题。例如,…...

研0 冲刺算法竞赛 day29 P2249 【深基13.例1】查找
P2249 【深基13.例1】查找 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 思路: ①二分查找 ②stl函数:lower_bound(a.begin(),a.end(),x) 返回第一个大于等于 x的数的地址 代码: #include<iostream> #include<algorithm> …...

基于vue框架的CKD电子病历系统nfa2e(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
系统程序文件列表 项目功能:患者,医生,药品信息,电子病历,临时医嘱,长期医嘱,健康科普 开题报告内容 基于Vue框架的CKD电子病历系统 开题报告 一、选题背景 随着信息技术的飞速发展和医疗信息化的深入推进,电子病历系统(Electronic Medic…...
笔记:python 安装tar包报错
报错信息 ERROR: Could not find a version that satisfies the requirement setuptools>40.8.0 (from versions: none)ERROR: No matching distribution found for setuptools>40.8.0分析 1,当前已安装 setuptools 并且版本超过40.8.0 解决方案 缺包了&am…...
575. 分糖果
哈喽!大家好,我是奇哥,一位专门给面试官添堵的职业面试员 文章持续更新,可以微信搜索【小奇JAVA面试】第一时间阅读,回复【资料】更有我为大家准备的福利哟! 文章目录 一、题目二、答案三、总结 一、题目 …...

手机电量消耗分析工具 Battery Historian 指南
阅读五分钟,每日十点,和您一起终身学习,这里是程序员Android 本篇文章主要介绍 Android 开发中 电量 的部分知识点,通过阅读本篇文章,您将收获以下内容: 一、安装Battery Historian二、收集Batterystats 数据三、使用B…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...

Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...

C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...

TSN交换机正在重构工业网络,PROFINET和EtherCAT会被取代吗?
在工业自动化持续演进的今天,通信网络的角色正变得愈发关键。 2025年6月6日,为期三天的华南国际工业博览会在深圳国际会展中心(宝安)圆满落幕。作为国内工业通信领域的技术型企业,光路科技(Fiberroad&…...
HTML前端开发:JavaScript 获取元素方法详解
作为前端开发者,高效获取 DOM 元素是必备技能。以下是 JS 中核心的获取元素方法,分为两大系列: 一、getElementBy... 系列 传统方法,直接通过 DOM 接口访问,返回动态集合(元素变化会实时更新)。…...
API网关Kong的鉴权与限流:高并发场景下的核心实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言 在微服务架构中,API网关承担着流量调度、安全防护和协议转换的核心职责。作为云原生时代的代表性网关,Kong凭借其插件化架构…...