MindSpore风险剖析与测试指南

概述

人工智能(AI)框架已经有近10年的发展历史,四条主线驱动着AI框架不停地演进和发展:

  1. 面向开发者:兼顾算法开发的效率和运行性能。
  2. 面向硬件:充分发挥芯片和集群的性能。
  3. 面向算法和数据:从计算规模看,需要应对模型越来越大的挑战;从计算范式看,需要处理不断涌现的新的计算负载。
  4. 面向部署:需要将AI能力部署到每个设备、每个应用、每个行业。

MindSpore是面向“端-边-云”全场景设计的AI框架,旨在弥合AI算法研究与生产部署之间的鸿沟。

在算法研究阶段,为开发者提供动静统一的编程体验以提升算法的开发效率;在生产阶段,自动并行可以极大加快分布式训练的开发和调试效率,同时充分挖掘异构硬件的算力;在部署阶段,基于“端-边-云”统一架构,应对企业级部署和安全可信方面的挑战。

概述

正常业务流程具体如图所示:

执行流程

左边蓝色方框的是MindSpore主体框架,主要提供神经网络在训练、验证相关的基础API功能,另外还会默认提供自动微分、自动并行等功能。

蓝色方框往下是MindSpore Data模块,可以利用该模块进行数据预处理,包括数据采样、数据迭代、数据格式转换等不同的数据操作。在训练的过程会遇到很多调试调优的问题,因此有MindSpore Insight模块对loss曲线、算子执行情况、权重参数变量等调试调优相关的数据进行可视化,方便用户在训练过程中进行调试调优。

AI安全最简单的场景就是从攻防的视角来看,例如,攻击者在训练阶段掺入恶意数据,影响AI模型推理能力,于是MindSpore推出了MindSpore Armour模块,为MindSpore提供AI安全机制。

蓝色方框往上的内容跟算法开发相关的用户更加贴近,包括存放大量的AI算法模型库ModelZoo,提供面向不同领域的开发工具套件MindSpore DevKit,另外还有高阶拓展库MindSpore Extend,这里面值得一提的就是MindSpore Extend中的科学计算套件MindSciences,MindSpore首次探索将科学计算与深度学习结合,将数值计算与深度学习相结合,通过深度学习来支持电磁仿真、药物分子仿真等等。

神经网络模型训练完后,可以导出模型或者加载存放在MindSpore Hub中已经训练好的模型。接着有MindIR提供端云统一的IR格式,通过统一IR定义了网络的逻辑结构和算子的属性,将MindIR格式的模型文件 与硬件平台解耦,实现一次训练多次部署。因此如图所示,通过IR把模型导出到不同的模块执行推理。

整体架构

MindSpore整体架构及后端相关组件如下图所示:

整体架构

MindSpore整体架构包括如下几个主要组件,它们之间存在相互的依赖关系:

  • Python API:提供了基于Python的前端表达与编程接口,支撑用户进行网络构建、整图执行、子图执行以及单算子执行,并通过pybind11接口调用到C++模块,C++模块分为前端、后端、MindData、Core等;
  • MindExpression前端表达:负责编译流程控制和硬件无关的优化如类型推导、自动微分、表达式化简等;
  • MindData数据组件:MindData提供高效的数据处理、常用数据集加载等功能和编程接口,支持用户灵活的定义处理注册和pipeline并行优化;
  • MindIR:包含了ANF IR数据结构、日志、异常等端、云共用的数据结构与算法。

大致可以分为四层:

  1. 模型层,为用户提供开箱即用的功能,该层主要包含预置的模型和开发套件,以及图神经网络(GNN)、深度概率编程、科学计算库等热点研究领域拓展库;
  2. 表达层 (MindExpression),为用户提供AI模型开发、训练、推理的接口,支持用户用原生 Python语法开发和调试神经网络,其特有的动静态图统一能力使开发者可以兼顾开发效率和执行性能,同时该层在生产和部署阶段提供全场景统一的C++/Python接口;
  3. 编译优化(MindCompiler),作为AI框架的核心,以全场景统一中间表达(MindIR)为媒介,将前端表达编译成执行效率更高的底层语言,同时进行全局性能优化,包括自动微分、代数化简等硬件无关优化,以及图算融合、算子生成等硬件相关优化;
  4. 运行时,按照上层编译优化的结果对接并调用底层硬件算子,同时通过“端-边-云”统一的运行时架构,支持包括联邦学习在内的“端-边-云”AI协同。

安装MindSpore

可以参照官方文档,因配合后续模糊测试,采用源码编译方式安装MindSpore CPU版本。

环境准备-手动

下表列出了编译安装MindSpore所需的系统环境和第三方依赖。

软件名称 版本 作用
Ubuntu 18.04 编译和运行MindSpore的操作系统
Python 3.7-3.9 MindSpore的使用依赖Python环境
wheel 0.32.0及以上 MindSpore使用的Python打包工具
setuptools 44.0及以上 MindSpore使用的Python包管理工具
GCC 7.3.0到9.4.0之间 用于编译MindSpore的C++编译器
git - MindSpore使用的源代码管理工具
CMake 3.18.3及以上 编译构建MindSpore的工具
tclsh - MindSpore sqlite编译依赖
patch 2.5及以上 MindSpore使用的源代码补丁工具
NUMA 2.0.11及以上 MindSpore使用的非一致性内存访问库
LLVM 12.0.1 MindSpore使用的编译器框架(可选,图算融合以及稀疏计算需要)

下面给出第三方依赖的安装方法。

安装Python

Python可通过多种方式进行安装。

  • 通过Conda安装Python。

    安装Miniconda:

    1
    2
    3
    4
    5
    6
    cd /tmp
    curl -O https://mirrors.tuna.tsinghua.edu.cn/anaconda/miniconda/Miniconda3-py37_4.10.3-Linux-$(arch).sh
    bash Miniconda3-py37_4.10.3-Linux-$(arch).sh -b
    cd -
    . ~/miniconda3/etc/profile.d/conda.sh
    conda init bash

    安装完成后,可以为Conda设置清华源加速下载,参考此处

    创建虚拟环境,以Python 3.7.5为例:

    1
    2
    conda create -n mindspore_py37 python=3.7.5 -y
    conda activate mindspore_py37

  • 通过APT安装Python,命令如下。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    sudo apt-get update
    sudo apt-get install software-properties-common -y
    sudo add-apt-repository ppa:deadsnakes/ppa -y
    sudo apt-get install python3.7 python3.7-dev python3.7-distutils python3-pip -y
    # 将新安装的Python设为默认
    sudo update-alternatives --install /usr/bin/python python /usr/bin/python3.7 100
    # 安装pip
    python -m pip install pip -i https://repo.huaweicloud.com/repository/pypi/simple
    sudo update-alternatives --install /usr/bin/pip pip ~/.local/bin/pip3.7 100
    pip config set global.index-url https://repo.huaweicloud.com/repository/pypi/simple

    若要安装其他Python版本,只需更改命令中的3.7

可以通过以下命令查看Python版本。

1
python --version

安装wheel和setuptools

在安装完成Python后,使用以下命令安装。

1
2
pip install wheel
pip install -U setuptools

安装GCC git tclsh patch和NUMA

可以通过以下命令安装GCC,git,tclsh,patch和NUMA。

1
sudo apt-get install gcc-7 git tcl patch libnuma-dev -y

如果要安装更高版本的GCC,使用以下命令安装GCC 8。

1
sudo apt-get install gcc-8 -y

或者安装GCC 9。

1
2
3
4
sudo apt-get install software-properties-common -y
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo apt-get update
sudo apt-get install gcc-9 -y

安装CMake

可以通过以下命令安装CMake

1
2
3
wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | sudo apt-key add -
sudo apt-add-repository "deb https://apt.kitware.com/ubuntu/ $(lsb_release -cs) main"
sudo apt-get install cmake -y

安装LLVM-可选

可以通过以下命令安装LLVM

1
2
3
4
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo add-apt-repository "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-12 main"
sudo apt-get update
sudo apt-get install llvm-12-dev -y

从代码仓下载源码

1
git clone https://gitee.com/mindspore/mindspore.git

编译MindSpore

进入mindspore根目录,然后执行编译脚本。

1
2
cd mindspore
bash build.sh -e cpu -j4 -S on

其中:

  • 如果编译机性能较好,可在执行中增加-j{线程数}来增加线程数量。如bash build.sh -e cpu -j12
  • 默认从github下载依赖源码,当-S选项设置为on时,从对应的gitee镜像下载。
  • 关于build.sh更多用法请参看脚本头部的说明。

安装MindSpore

1
pip install output/mindspore-*.whl -i https://pypi.tuna.tsinghua.edu.cn/simple

在联网状态下,安装whl包时会自动下载mindspore安装包的依赖项(依赖项详情参见setup.py中的required_package),其余情况需自行安装。运行模型时,需要根据ModelZoo中不同模型指定的requirements.txt安装额外依赖,常见依赖可以参考requirements.txt

验证安装是否成功

1
python -c "import mindspore;mindspore.set_context(device_target='CPU');mindspore.run_check()"

如果输出:

1
2
MindSpore version: 版本号
The result of multiplication calculation is correct, MindSpore has been installed on platform [CPU] successfully!

说明MindSpore安装成功了。

升级MindSpore版本

在源码根目录下执行编译脚本build.sh成功后,在output目录下找到编译生成的whl安装包,然后执行下述命令进行升级。

1
pip install --upgrade mindspore-*.whl

威胁分析与模糊测试

通过业界对AI框架软件Tensorflow安全研究成果和上述的整体架构,抽取出MindSpore所面临的安全风险和漏洞模式。

TensorFlow的系统结构以C API为界,将整个系统分为「前端」和「后端」两个子系统:

  • 前端系统:提供编程模型,负责构造计算图;

  • 后端系统:提供运行时环境,负责执行计算图。

Tensorflow架构

如上图所示,重点关注系统中如下4个基本组件,它们是系统分布式运行机制的核心。

  1. Client Client是前端系统的主要组成部分,它是一个支持多语言的编程环境。它提供基于计算图的编程模型,方便用户构造各种复杂的计算图,实现各种形式的模型设计。Client通过Session为桥梁,连接TensorFlow后端的「运行时」,并启动计算图的执行过程。

  2. Distributed Master 在分布式的运行时环境中,Distributed Master根据Session.run的Fetching参数,从计算图中反向遍历,找到所依赖的「最小子图」。然后,Distributed Master负责将该「子图」再次分裂为多个「子图片段」,以便在不同的进程和设备上运行这些「子图片段」。最后,Distributed Master将这些「子图片段」派发给Work Service;随后Work Service启动「子图片段」的执行过程。

  3. Worker Service 对于每以个任务,TensorFlow都将启动一个Worker Service。Worker Service将按照计算图中节点之间的依赖关系,根据当前的可用的硬件环境(GPU/CPU),调用OP的Kernel实现完成OP的运算(一种典型的多态实现技术)。另外,Worker Service还要负责将OP运算的结果发送到其他的Work Service;或者接受来自其他Worker Service发送给它的OP运算的结果。

  4. Kernel Implements Kernel是OP在某种硬件设备的特定实现,它负责执行OP的运算。

通过对业界Tensorflow漏洞进行分析,可总结出主要漏洞模式为构造恶意参数传递给python API,恶意参数通过数据流传递到后端C++内核,导致后端出现传统编码错误。因此我们可以将模糊测试的重点放在算子和模型转换与解析,分别对应MindSpore的api接口以及MindSpore Lite的converter工具,模糊测试工具我们选择Atheris: A Coverage-Guided, Native Python Fuzzer以及AFLPlusPlus(或者honggfuzz等)。

编译插桩版本

MSLITE编译

1
2
3
4
5
6
7
8
9
10
11
12
export CFLAGS=-w
export CXXFLAGS=-w
export CC=afl-gcc-fast
export CXX=afl-g++-fast
export MSLITE_ENABLE_TRAIN=off
export MSLITE_ENABLE_CONVERTER=on
export MSLITE_ENABLE_TOOLS=on
export MSLITE_ENABLE_MODEL_OBF=on
export MSLITE_ENABLE_MODEL_ENCRYPTION=on
export MSLITE_ENABLE_MODEL_PRE_INFERENCE=on

bash build.sh -I x86_64 -d -a on -j$(nproc)

MindSpore编译

1
2
3
4
5
6
export CFLAGS=-w
export CXXFLAGS=-w
export CC=gcc
export CXX=g++

bash build.sh -e cpu -d -c on -a on -j$(nproc)

进行模糊测试

使用Atheris对python API测试

参照python_fuzzing.py编写辅助测试脚本

构造恶意Tensor对算子测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
"""This is a Python API fuzzer template for mindspore.ops.abs"""
import atheris

with atheris.instrument_imports():
import sys
from python_fuzzing import FuzzingHelper
import mindspore as ms


def TestOneInput(data):
"""Test randomized fuzzing input for tf.raw_ops.Abs."""
fh = FuzzingHelper(data)

input_tensor = fh.get_random_numeric_tensor(dtype=ms.float32)

_ = ms.ops.abs(input=input_tensor)


def main():
atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()


if __name__ == "__main__":
main()

构造恶意模型对加载接口测试

编写MINDIR的proto文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
syntax = "proto2";
package mind_ir;

enum Version {
IR_VERSION_START = 0;
IR_VERSION = 1;
}

message AttributeProto {
enum AttributeType {
UNDEFINED = 0;
FLOAT = 1;
UINT8 = 2;
INT8 = 3;
UINT16 = 4;
INT16 = 5;
INT32 = 6;
INT64 = 7;
STRING = 8;
BOOL = 9;
FLOAT16 = 10;
DOUBLE = 11;
UINT32 = 12;
UINT64 = 13;
COMPLEX64 = 14;
COMPLEX128 = 15;
BFLOAT16 = 16;
TENSOR = 17;
GRAPH = 18;
TENSORS = 19;
TUPLE = 20; // tuple
LIST = 21; // list
DICT = 22; // dictionary
UMONAD = 23;
IOMONAD = 24;
NONE = 25;
PRIMITIVECLOSURE = 26;
FUNCGRAPHCLOSURE = 27;
PARTIALCLOSURE = 28;
UNIONFUNCCLOSURE = 29;
CSR_TENSOR = 30;
COO_TENSOR = 31;
ROW_TENSOR = 32;
CLASS_TYPE = 33;
NAME_SPACE = 34;
SYMBOL = 35;
TYPE_NULL = 36;
MAP_TENSOR = 37;
FUNCTOR = 38;
SCALAR = 39;
}
message SeqInfoProto{
optional bool is_dyn_len = 1; // store if tuple is dynamic length
optional AttributeProto tuple_elem_item = 2; // store the element of tuple dynamic length
}
optional string name = 1;
optional float f = 2;
optional int64 i = 3;
optional double d = 4;
optional bytes s = 5;
optional TensorProto t = 6;
optional GraphProto g = 7;
repeated float floats = 8;
repeated double doubles = 9;
repeated int64 ints = 10;
repeated bytes strings = 11;
repeated TensorProto tensors = 12;
repeated GraphProto graphs = 13;
optional string doc_string = 14;
optional string ref_attr_name = 15;
optional AttributeType type = 16;
repeated AttributeProto values = 17; // tuple, list, dict of value
optional SeqInfoProto seq_info = 18; // tuple, list, structural info
optional FunctorProto functor = 19;
}

message FunctorProto {
enum FunctorType {
SHAPE_CALC_FUNCTOR = 1;
}
optional FunctorType type = 1;
optional string name = 2;
repeated AttributeProto values = 3;
}

message ValueInfoProto {
optional string name = 1;
repeated TensorProto tensor = 2;
optional string doc_string = 3;
optional string denotation = 4;
optional AttributeProto attr_info = 5; // graph input info for other type
}


message NodeProto {
repeated string input = 1;
repeated string output = 2;
optional string name = 3;
optional string op_type = 4;
repeated AttributeProto attribute = 5;
optional string doc_string = 6;
optional string domain = 7;
repeated AttributeProto node_attr = 8;
repeated AttributeProto primal_attr = 9;
}


message ModelProto {
optional string ir_version = 1;
optional string producer_name = 2;
optional string producer_version = 3;
optional string domain = 4;
optional string model_version = 5;
optional string doc_string = 6;
optional GraphProto graph = 7;
repeated GraphProto functions = 8; // all the graphs without the main graph.
optional PreprocessorProto preprocessor = 9; // data graph from MindData.
optional bool little_endian = 10; // bytes order in load device.
optional ParallelProto parallel = 11; // information for parallel.
repeated PrimitiveProto primitives = 12; // all the primitives of the model.
optional int64 mind_ir_version = 13;
}


message PreprocessorProto {
repeated PreprocessOpProto op = 1;
}


message PreprocessOpProto {
optional string input_columns = 1;
optional string output_columns = 2;
optional string project_columns = 3;
optional string op_type = 4;
optional string operations = 5;
optional bool offload = 6;
}


message GraphProto {
repeated NodeProto node = 1;
optional string name = 2;
repeated TensorProto parameter = 3;
optional string doc_string = 4;
repeated ValueInfoProto input = 5;
repeated ValueInfoProto output = 6;
optional string bprop_hash = 7;
repeated AttributeProto attribute = 8;
optional string bprop_filepath = 9;
repeated MapTensorProto map_parameter = 10;
}


message TensorProto {
enum DataType {
UNDEFINED = 0;
// Basic types.
FLOAT = 1; // float
UINT8 = 2; // uint8_t
INT8 = 3; // int8_t
UINT16 = 4; // uint16_t
INT16 = 5; // int16_t
INT32 = 6; // int32_t
INT64 = 7; // int64_t
STRING = 8; // string
BOOL = 9; // bool
FLOAT16 = 10;
DOUBLE = 11;
UINT32 = 12;
UINT64 = 13;
COMPLEX64 = 14;
COMPLEX128 = 15;
BFLOAT16 = 16;
FLOAT64 = 17;
}
enum CompressionType {
NO_COMPRESSION = 0;
INDEXING = 1;
SPARSE = 2;
FSE = 3;
BIT_PACKING = 4;
FSE_INT = 5;
FSE_INFER = 6;
}
message ExternalDataProto {
//POSIX filesystem path relative to the directory where the MindIR model was stored.
optional string location = 1;
optional int64 offset = 2;
optional int64 length = 3;
optional string checksum = 4;
}
message QuantParamProto {
required string quant_algo_name = 1;
repeated AttributeProto attribute = 2;
}
repeated int64 dims = 1;
optional int32 data_type = 2;
repeated float float_data = 3;
repeated int32 int32_data = 4;
repeated bytes string_data = 5;
repeated int64 int64_data = 6;
optional string name = 7;
optional string doc_string = 8;
optional bytes raw_data = 9;
repeated double double_data = 10;
repeated uint64 uint64_data = 11;
optional ExternalDataProto external_data = 12;
optional string ref_key = 13;
repeated int64 min_dims = 14;
repeated int64 max_dims = 15;
optional CompressionType compression_type = 16;
repeated QuantParamProto quant_params = 17;
}

message MapTensorProto {
required string name = 1;
required AttributeProto default_value = 2;
required TensorProto key_tensor = 3;
required TensorProto value_tensor = 4;
required TensorProto status_tensor = 5;
}

message ParallelProto {
repeated LayoutProto layout = 1;
}

message LayoutProto {
optional string name = 1;
repeated int64 device_arrangement_int = 2;
repeated int64 tensor_map_int = 3;
repeated int64 slice_shape_int = 4;
optional int64 field_size = 5;
optional bool uniform_split = 6;
optional string opt_shard_group = 7;
}

message PrimitiveProto {
optional string name = 1;
optional string op_type = 2;
repeated AttributeProto attribute = 3;
optional string instance_name = 4;
}

使用Libprotobuf-mutator辅助测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
"""This is a Python API fuzzer template with protobuf for mindspore.load"""

import atheris
import sys
import numpy as np
import os

import atheris_libprotobuf_mutator

import mind_ir

with atheris.instrument_imports():
import mindspore as ms

_DEFAULT_FILENAME = '/tmp/test.mindir'

@atheris.instrument_func
def TestOneProtoInput(data):
with open(_DEFAULT_FILENAME,mode='w') as f:
f.write(data.SerializeAsString())
try:
_ = ms.load(filename = _DEFAULT_FILENAME)
except:
return

if __name__ == '__main__':
atheris_libprotobuf_mutator.Setup(
sys.argv, TestOneProtoInput, proto=mind_ir.ModelProto)
atheris.Fuzz()

atheris的命令行参数与libfuzzer一致,参照官方文档配置即可。

使用AFL对端侧推理框架测试

配置环境变量,以converter为例进行fuzz

1
2
3
export LD_LIBRARY_PATH=$PWD/output/tmp/mindspore-lite-2.1.0-linux-x64/runtime/lib:$PWD/output/tmp/mindspore-lite-2.1.0-linux-x64/tools/converter/lib

afl-fuzz -i mindir_corpus -o outdir -- ./output/tmp/mindspore-lite-2.1.0-linux-x64/tools/converter/converter/converter_lite --fmk=MINDIR --modelFile=@@ --outputFile=/dev/null

MindSpore风险剖析与测试指南
https://mundi-xu.github.io/2023/07/26/Security-Risk-Analysis-of-Huawei-Mindspore/
Author
寒雨
Posted on
July 26, 2023
Licensed under