0


逻辑这回事(二)----FPGA安全编码规范

安全编码的背景、定义

FPGA攻击方式和攻击目的

安全编码价值

2020年4月,来自德国的研究者披露了一个名为“StarBleed”的漏洞,当时引起了业内一片轰动。这种漏洞存在于赛灵思的Virtex、Kintex、Artix、Spartan 等全部7系列FPGA中。通过这个漏洞,攻击者可以同时攻破FPGA配置文件的加密(confidentiality)和鉴权(authenticity),并由此可以随意修改FPGA中实现的逻辑功能。攻击者通过篡改bit流实现对FPGA加载关键配置寄存器的修改,通过蚂蚁搬家的形式,从jtag接口获取bit明文。更严重的是,这个漏洞并不能通过软件补丁的方式修复,一旦某个芯片被攻破,就只能通过更换芯片的方式修复。

漏洞的发现者于2019年9月将这个漏洞知会了赛灵思,并在第二天就获得了赛灵思的承认。根据赛灵思之前发布的财报,7系列FPGA贡献了公司35%的营收。这些FPGA被广泛用于通信设备、医疗、军工宇航等多个领域,而这些领域很多都需要系统有着很高的稳定性与安全性。因此,这次爆出的重大漏洞,对赛灵思及其客户带来较大的负面影响。

图1 攻击过程

加密指的是使用特定算法对比特流文件进行处理,将其转换成密文,使得其中的内容对外不可见。在赛灵思的7系列FPGA中,使用了CBC-AES-256算法进行比特流加密。鉴权指的是对加密后的比特流文件进行身份验证,防止对其进行篡改和删减,这类似于我们日常生活中的身份验证。如果比特流文件被修改,势必会导致错误的鉴权结果。如果将这个比特流下载到FPGA中,会因为身份校验失败而拒绝执行,从而避免被攻击的可能。在赛灵思的7系列FPGA中,使用了基于SHA-256的HMAC(散列消息认证码,Hash-based Message Authentication Code)方法进行鉴权。如果比特流的加密过程被破解,那么攻击者就可以读出比特流文件中的所有信息,从而进行反向工程、IP破解、信息收集等工作。如果鉴权过程被破解,那么攻击者就可以对比特流文件进行任意修改,比如修改系统功能、木马注入等。所以说,这两种保护方式缺一不可。赛灵思FPGA被攻破的根本原因是可信编码存在不足:

(1)违反接口鉴权原则:鉴权过程晚于加密过程,鉴权功能没有生效。

(2)违反调试接口关断原则:违反调试接口(串口、JTAG等)需要关断,禁止通过配置接口读取敏感信息。

(3)违反接口配置合法性检查原则:Wbstar寄存器器操作命令合法性检测未生效,导致攻击者可以修改操作命。

(4)身份校验的密钥直接存储在加密后的比特流文件里,且无额外加密。

因此,逻辑的安全性不仅取决于方案/加密算法,还取决于可信编码实现细节。

FPGA攻击主要目的
  1. 窃取知识产权:如窃取FPGA bit流进行反向工程,窃取自研IP CORE;
  2. 拒绝服务:植入恶意代码,如通过对第三方IP CORE恶意植入、FPGA bit流部分重构植入恶意代码;业务中断/系统瘫痪,如输入异常报文、异常配置、环境异常、恶意修改规则库/知识库等造成业务中断/系统瘫痪;
  3. 窃取信息:访问权限篡改,如通过权限篡改实现非安全CPU访问安全CPU才能访问的资源等;窃取敏感信息,如窃取密钥、口令等;
FPGA攻击方式
侧信道攻击

芯片在运行的时候,由于数据或者逻辑的不同,内部的晶体管通断是有区别的,通过这个区别来确定程序内部的数据或者指令,就是侧信道攻击。获取这个区别有很多方法,比如在芯片的GND引脚处获取电压,通过探针去截取芯片辐射的变化等等。再利用密码算法类逻辑在运行过程中的功耗泄露,从泄露功耗信息中恢复秘钥的方法。

图xx通过功耗采集和算法分析破解秘钥

SPA破解加密算法的一个著名例子是运行RSA(Rivest-Shamir-Adleman)的智能卡。RSA 是一种公钥加密算法,它通过在加密密钥上执行一系列模块化幂循环来运行,每个循环由许多乘法和平方运算组成。众所周知,在幂循环的每次迭代中都会执行平方运算,而乘法仅在指数位为 1 时才执行。

             图xx 来自RSA算法的SPA泄漏

从功率迹线可以看出,额外的乘法运算会消耗更多的功率,这会导致功率迹线中的尖峰更高。根据这条轨迹,以及我们对RSA算法的了解,我们可以简单地确定每个功率尖峰(即乘法运算)与密钥指数中的“1”位相关。在这种情况下,“0”显示为较短的凸起,而没有随后较高的凸起。通过这种方式,SPA能够从RSA恢复秘密加密密钥。

故障注入

故障注入攻击,指在密码芯片设备中通过在密码算法中引入错误,导致密码设备产生错误结果,对错误结果进行分析从而得到密钥。

时钟、电压注错破解秘钥

通过时钟、电压、电磁或者激光等方式在密码算法逻辑运行过程中注入故障错误,导致逻辑产生错误结果,对错误结果进行分析从而恢复密钥的攻击方法。

Bit窃取、恶意注入

bit窃取是读取存储在FPGA外部存储器中的bit流,利用逆向分析,获得设计方案。恶意注入将bit流进行部分重构后再加载,进行恶意代码植入。

逻辑攻击

攻击者利用业务接口或配置接口输入异常报文进行攻击,使业务中断/系统瘫痪,或者获取 FPGA中的敏感信息的攻击方法。

FPGA威胁防护和可信编码定义

FPGA受到的威胁可分为下面6种:

本规范主要解决可信编码的问题,包括可靠性编码规则、基础安全编码规则、高级编码规则三部分内容,提升电路可靠性、消除接口逻辑攻击、侧信道/故障注入威胁。

面临威胁

FPGA****防护方法

FPGA****防护方法类别

Bit窃取

加密加载和防止回读方法

FPGA器件升级

恶意注入

加密加载和鉴权启动方式

FPGA器件升级

物理攻击

通过添加屏蔽层,存储加密等方式,防止攻击通过芯片逆向、探针探测、FIB等方式攻击

FPGA器件升级

环境失效、人为疏忽

可靠性设计等方式

Verilog可靠性编码规则

逻辑攻击

对配置接口检测、访问权限管理、鉴权等方式

Verilog基础安全编码规则

侧信道攻击

采用掩码、随机化算法等方式

Verilog高级安全编码规则

故障注入攻击

通过冗余设计,sensor检测等方式

Verilog高级安全编码规则

可靠性编码规则

可靠性编码规则用于解决基础电路的可靠性问题。基础电路应对逻辑典型故障模式,包括设计者人为疏忽、外部应力异常、接口异常。

语法、时钟、复位、异步处理规则

规则1:语法--信号位宽显示表达、向量位宽需要匹配

【故障影响】位宽不匹配时,综合工具会自动做隐式位宽扩展,可能综合出与设计期望不符的电路。

【解决方法】向量、参数和信号的位宽必须清晰的表示出来;两个向量进行比较、加减或赋值操作时位宽要相等。

【异常代码示例】

reg [7:0] cnt:

reg [7:0] cnt_tmp:

if ( cnt tmp == ( cnt - 1))//当cnt-1直接用作为条件判断时,1被当成了32位整型,当cnt=0时,cnt-1被扩展为32’hffff ,所以 cnttmp ==cnt-1条件不会成立

【正确代码示例】

reg [7:0] cnt ;

reg [7:0] cnt tmp ;

if ( cnt_tmp == ( cnt - 1’ b1)//加减常数操作,必须显式写出常数的位宽

规则2:语法--组合逻辑敏感向量必须完备

【故障影响】always语句敏感列表不完整会导致逻辑代码功能异常。

【解决方法】组合逻辑always中敏感表罗列完备敏感变量,或使用*替代。

【异常代码示例】

always @( a or b) begin //敏感表中缺少信号c,会导致仿真功能异常

sig_out=a&b&c;

end

【正确代码示例】

always @( a or b or c) begin

sig_out2 =a&b&c:

end

或者

always @( *) //推荐该写法,不易出错 begin

sig_out3 =a&b&c:

end

可以看到最后vivado综合的结果sig_out、sig_out2、sig_out3都是一样的。

但是仿真结果sig_out2、sig_out3是我们设计的初衷,sig_out行为不符合要求。

敏感列表是否完整是给仿真器看的。always块敏感列表如果不完整,不会对综合有任何影响,综合器处理always块判断这是register还是latch还是组合的判断链中,只看敏感列表是边沿触发还是电平触发,根本不看电平触发里面的内容。但敏感列表还是不能乱写或者写不完整,也不能多写(影响仿真速度),因为仿真器是会看具体内容的。

规则3:语法--assign选择语句只写一层,多层用always

【故障影响】使用多层assign语句,代码逻辑关系表达不清渐,容易出错。

【异常代码示例】

assign data out = (sel_a == 1'b1 )? data_a:

( sel_b == 1'b1 )? data_b:data_c : //多层造择语句,可读性差,容易出错

【解决方法】简单组合逻辑采用assign的写法;复杂组合逻辑采用always语句

【正确代码示例】

always @( *)

begin

if ( sel_a == 1'b1 )

data_out = data_a

else if ( sel_b == 1'b1 )

data_out = data _b

else

data_out = data._c

end

规则4:语法--case语句需要有default

【故障影响】case语句不使用default在候选项不完备时,存在对异常输入处理不当,以及可能综合出非预期电路的风险

【异常代码示例】

always @( posedge clk_sys) begin

case(number) //由于没有default ,综合结果是保持,异常输入产生正常的输出结果。

3’b000:data_bitmap <=4'b1000;

3'b001:data_bitmap <=4'b0001;

3'b011:data_bitmap <=4'b0100;

endcase

end

【解决方法】case语句中必须使用default,用于显式指示表达式的值在不符合所有候选项时,需要执行的语句。

【正确代码示例】

always @( posedge clk_sys) begin

case(number)

3’b000:data_bitmap <=4'b1000;

3'b001:data_bitmap <=4'b0001;

3'b011:data_bitmap <=4'b0100;

default:data_bitmap <=4'b0000;

endcase

end

基本电路规则

规则1:基本电路--门控、时钟选择电路优先使用原语

【故障影响】直接使用组合逻辑实现的门控时钟、时钟选择器,容易产生毛刺,使驱动的逻辑电路误动作。

【异常代码示例】

assign ck_out = (clk_en == 1'b1) ? clk_in : 1'b0;

【解决方法】直接使用具有glitch-free特性的器件原语来实现,比如bufgce、bufgmux等,以保证输出的时钟无毛刺。

【正确代码示例】

BUFGCE u_bufg_ce (

.I ( clk_in ),

.0 ( clk_out ),

.CE( clk en )

);

规则2:基本电路--时钟需要支持有无检测和频偏检测

【故障影响】时钟是同步设计基础;时钟异常时有可能导致逻辑功能异常。

【解决方法】时钟需要支持有无检测和频偏检测,在时钟异常时上报告警,系统可以进行自愈。

【正确代码示例】

reg clk_16div: //被检测时钟16分频的信号

always @(posedge clk_chk) begin// clk_chk为CPU时钟

clk_16div_1sync <= clk_16div;

clk_16div_2sync <= cik_16div_1sync;

clk_16div_3sync <= clk_16div_2sync;

end

assign clk_16div_pos = (dlk_16div_2sync == 1'bl && clk_16div_3sync == 1'b0);

assign clk_loss = (clk_cnt > MAX_VALUE) ? 1'b1 : 1'b0;

//代码实现时钟有无检测功能,用待检信号的分频信号对基准计数器清零

always @(posedge rst_chk or posedge clk_chk) begin

if (rst_chk == 1'b1)

clk_cnt <= 8'b0;

else if (clk_chk_en == 1'b0 || clk_16div_pos == 1'b1)

clk_cnt <=8'b0;

else if (clk_loss == 1'b0)

clk_cnt <= clk_cnt + 1'b1;

else

;

end

规则3:基本电路--内部复位需要经过同步化处理

【故障影响】异步复位容易触发亚稳态问题;复位撤销时刻不同,导致寄存器组解复位时刻不同。

【异常代码示例】 异步复位导致链表初始化异常案例

always @(posedge rst or posedge sys_clk) begin

if (rst == 1'bl) begin

ram_wen <= #U_DLY 1'b0:

ram_addr <= #U_DLY 10'b0;

ram_dataw <= #U_DLY 45'b0;

sequeue_table_state <= #U_DLY QUEUE_TABLE_INIT;

end

else if (sequeue_table_state) begin

             ram_wen <= #U_DLY 1'b1;

             ram_addr <= #U_DLY Ram_addr + 1'b1;

             ram_dataw <= #U_DLY {15'h7fff,15'h7fff,15'h000};

end else

问题1:ram_dataw寄存器组解复位时刻不同,写入地址0的数据有可能是非法值,导致队列信息表的初始化错误。

问题2:ram_wen,ram_addr,ram_data解复位点不同,电路的起始工作时刻不同,破坏时序关系。

【解决方法】推荐采用异步复位同步撤离,同时逻辑内部不同时钟域尽量使用本时钟域同步化后的复位信号。

【正确代码示例】

always @(posedge ck_sys or posedge rst_ext_pin ) begin

if ( rst_ext_pin == 1'b1)

rst_sync <= 4'b1111

else

rst_sync <= {rst_sync[2:0], 1'b0};

end

规则4:基本电路--状态机异步解复位后不能立即跳转

【故障影响】在异步解复位时,状态机不能解复位后立即跳转,如果立即跳转,可能由于状态机不同bit信号存在亚稳态问题使状态机进入异常状怎。

【异常代码示例】

always @ ( posedge clk or posedge reset ) begin

if( reset == 1'b1 )

   curr_state <= FSM_IDLE;

else

   curr_state <=  next_state;

end

always @(*) begin

case(curr_state)

   FSM_IDLE: next_state = FSM_S1; //解复位后立即跳转

    FSM_S1: ...

    ......

   default:next_state = FSM_IDLE;

endcase

end

【解决方法】可以在解复位后延迟若干拍生成1bit的使能信号作为状态机的跳转条件,或者对异步复位信号做同步释放。

【正常代码示例】

always@( posedge clk or posedge reset ) begin

if( reset==1'b1) begin

    fsm_en <= 1'd0;

    fsm_en_1d <= 1'd0;

    fsm_en_2d <= 1'd0;

end

else

begin

    fsm_en <= 1'd1;

    fsm_en_1d <= fsm_en;

    fsm_en_2d <= fsm_en_1d;

end

always@(*) begin

case ( curr_state)

    FSM_IDLE:

       if (fsm_en_2d == 1'd1) //使用fsm_en_2d使能信号作为状态机的跳转条件

          next_state = FSM_S1;

       else

          next_state = FSM_IDLE;

    FSMS1:......

    default: next_state = FSM_IDLE; 

endcase

end

规则6:基本电路--控制信号需要有复位端

【故障影响】如果没有复位端,则每次FPGA上电,寄存器可能为0,也可能为1,导致使用该寄存器的电路出现异常或者挂死。

【解决方法】对于控制逻辑寄存器必须有复位端,以避免电路初始态不确定。

【异常代码示例】

always @ ( posedge clk)

if((wr_addr == ADDR_WORK_EN) && (wr_data[0]== 1'b1)) 

   work_enable <= 1'b1; 

//work_enable上电后有可能是0或1,导致电路误动作

【正确代码示例】

always @ ( posedge clk or posedge rst )

if (rst == 1'b1)

   work work_enable <= 1'b0;

else if((wr_addr == ADDR_WORK_EN) && (wr_data[0]== 1'b1))

   work_enable <= 1'b1; 

规则7:基本电路—跨时钟域单bit信号同步后使用

跨时钟域处理后面会有专门章节详细讲解,这里只举一个例子。

现象:以太网口子卡光模块,发送侧业务挂死。

光模块los信号未同步就作为状态机跳转信号,导致状态异常。

异常采样后,产生了亚稳态。

同一时刻,状态寄存器两个状态位跳转条件不同,导致状态机跳转异常。

【故障影响】异步信号跨时钟域处理,会导致亚稳态扩散,影响目的时钟域电路的功能。

【解决方法】通过至少2级以上寄存器级联可以解决一般性的亚稳态问题。

【正确代码示例】

reg signal_b; //时钟域clk b产生的信号

reg signal_b_1sync, signaL b_2sync;

always @( posedge clk_a) begin

signal_b_1sync <= signa_b;

signal_b_2sync <= signal_b_1sync;

end

always @(posedge clk_a ) begin

if(signal_b_2sync == 1'b1 )

plen_adj <= 4'h4;

else

plen_adj <= 4'h8;

end

规则8:基本电路—跨时钟域多bit信号同步后使用

【故障影响】多比特信号不进行跨时钟域处理,会使信号出现不确定态,影响目的时钟域电路的功能。

【解决方法】采用握手方式同步化,数据有效指示处于稳定期,进行采样,避免亚稳态。

【异常代码示例】

always @( posedge clk_b) begin

data_a_1sync <= data_a;//有可能在亚稳态窗采样data_a,得到不确定的数据

data_a_2sync <= data_a_1sync;

end

assign value_err = (data_a_2sync <= MAX_VALUE)?1'b0 : 1'b1 ;

【正常代码示例】

always @( posedge clk_a ) begin

data_a <= data_tmp;

data_vld_a <= data_vid_tmp;

end //data_a在data_vld_a有效后需保持若干拍

always @( posedge clk b ) begin

data_a_1sync <= data_a ;

data_vld_a_1sync <= data_vld_a ;

data_vld_a_2sync <= data_vld_a_1sync ;

data_vld_a_3sync <= data_vld_a_2sync;

end

always @( posedge clk_b ) begin

if ( data_vld_a_3sync == 1'b1 ) begin

   data_b <= data_a_1sync ;

end

else ;

end //使用同步后的data_vld延迟采样data

assign value_err = (data_b <= MAX_VALUE) ? 1'b0:1'b1;

规则9:基本电路--状态机必须要带default语句

【故障影响】状态机不带default语句,在输入或处理出现异常时,存在进入异常状态后无法跳出,导致状态机挂死的风险。

【解决方法】状态机必须包括default语句

【正常代码示例】

parameter FSM_IDLE=2'd0,

      FSM_S1 = 2'd1,

      FSMLS2 =2'd2;

always @ ( posedge clk or posedge reset ) begin

if( reset == 1'b1 )

   curr_state <= FSM_IDLE;

else

   curr_state <=  next_state;

end

always@(*) begin

case ( curr_state)

    FSM_IDLE:

       if (a_rdy == 1'b1) && (b_rdy == 1'b1))

          next_state = FSM_S1;

       else

          next_state = FSM_IDLE;

    FSM_S1:...

    FSM_S2:...

    default: next_state= FSM_IDLE; //必须有该状态

endcase

end

规则10:基本电路--状态机跳转条件和信号生成条件一致

【故障影响】如果不一致有可能出现状态机跳转而信号没有跳转,或信号跳转状态机没有跳转

【解决方法】可以将判断条件生成1 bit的信号,信号生成和状态机跳转共用该条件。

【正常代码示例】

//将判断条件生成1bit信号,同时用于状态机跳转和输出信号产生

assign fsm_start_flag = (a==1'b1 && b==1'b0 && c==1b1)?1'b1:1'b0;

always @(*) begin

case (curr_state)

   FSM_IDLE:

      if (fsm_start_flag == 1'b1)

          next_state = FSM_S1;

      else

          next_state = FSM_IDLE;

    ......

   default:next_state = FSM_IDLE;

endcase

end

always @ ( posedge rst or posedge clk ) begin

if ( rst == 1'b1 )

sig0 <= 1b0;

else

sig0 <= (curr_state== FSM_IDLE && fsm_start_flag==1'b1 )? 1 b1 : 1'b0;

end

规则11:基本电路--关键控制类计数器冗余备份

【故障影响】控制类计数器因器件软失效或被攻击出现异常时,往往会造成逻辑异常。

【解决方法】逻辑实现时,可以考虑对关键控制计数器进行冗余备份,出现异常时触发自愈。

【正常代码示例】

assign ctrl_flag =((sig_a == 1'b1) && (sig_b == 1'b0) && (sig_c == 1'b1));

always @(posedge clk or posedge rst) begin

if ( cnt_init_en == 1'b1)

   ctrl_0cnt <= 4'b0;

else if (ctrl_flag == 1'b1)

   ctrl_0cnt <= ctrl_0cnt + 1'b1 ;

end

//ctrl_1cnt的产生和ctrl_0cnt一致

always @(posedge clk or posedge rst) begin

if ( cnt_init_en == 1'b1)

   ctrl_1cnt <= 4'b0;

else if (ctrl_flag == 1'b1)

   ctrl_1cnt <= ctrl_1cnt + 1'b1 ;

end

//两个计数器进行比较

assign cnt_comp_ok = (ctrl_0cnt == ctrl_1cnt)? 1'b1:1'b0;

//产生告警上报

assign cnt_alarm = ~cnt_comp_ok;

//产生初始化计数器指示

always @(posedge clk or posedge rst) begin

if (cnt_comp_ok == 1'b1)

   cnt_init_en <= 1'b0;

else

   cnt_init_en <= 1'b1;

end

规则12:基本电路--整包计数器需要翻转保护

【故障影响】整包计数器未做相应的翻转保护,会导致多包或者残包,甚至系统挂死等异常。

【解决方法】整包计数器增加上下溢出保护。

【正常代码示例】

case({rd_eop,wr_eop})

2'b01:

   if(cnt < 4'd15)

      cnt <= cnt + 1'd1;

   else

      ;//上溢出保护

2'b10:

   if(cnt != 4'd0)

      cnt <= cnt - 1'd1;

   else

      ;//下溢出保护

default:;

endcase

规则12:基本电路--可重配置信号动态修改,逻辑不能挂死

【故障影响】支持动态修改配置的信号,如果没有保护设计,可能在修改配置时导致逻辑挂死

【解决方法】支持动态修改配置的信号,需要有相应的保护设计。

【异常代码示例】

//配置信号选择根据a_fifo状态启动状态机

assign fsm_start = (sel_cfg == 1'd0)?a_fifo_rdy:b_fifo_rdy;

always @(*) begin

case( curr_state) 

    FSMLIDLE:

       if (fsm_start == 1'd1) //a_fifo

          next_state =  FSM_RD; 

       else

          next_state = FSM_IDLE;

    ......

    FSM_WAIT:

       if (fsm_start == 1'd1) 

          next_state = FSM_PROC; //假设配置信号此时切换选择了b_fifo,但b_fifo没有rdy.

       else

          next_state = FSM WAIT;  //导致状态机死等。

   default: next_state = FSM_IDLE;

endcase

end

【正常代码示例】

 assign fsm_start_reg = (sel_cfg_reg == 1'd0)?a_fifo_rdy:b_fifo_rdy;

always@(posedge clk)

if(curr_state == FSM_IDLE)//在跳出idle状态时,将sel_cfg锁存

   sel_cfg_reg <= sel_cfg;//保证一次处理中sel_cfg配置值不变

always @(*) begin

case( curr_state) 

    FSMLIDLE:

       if (fsm_start == 1'd1) //a_fifo

          next_state =  FSM_RD; 

       else

          next_state = FSM_IDLE;

    ......

    FSM_WAIT:

       if (sel_cfg_reg == 1'd1)  //采用锁存后的信号判断

          next_state = FSM_PROC;         

       else

          next_state = FSM WAIT;  //导致状态机死等。

   default: next_state = FSM_IDLE;

endcase

end

【总结】处理周期的起始点锁存动态配置信号,避免配置周期中间动态修改,导致关键信号跳变。

规则13:基本电路--组合逻辑所有条件分支必须赋值

【故障影响】条件分支或赋值语句不完备会产生不必要的锁存器,锁存器具有危害性;

【解决方法】所有的条件分支都必须有对的输出赋值,并且条件赋值语句必须写全

【异常代码示例】

always @(*) begin

if( ptype ==ETH_PKT)        

   plen_adj = 4'h4;             

else if( ptype == PPPOA_PKT)

   plen_adj = 4'h8;     

 //条件不完备,等效成给自身赋值,生产锁存器       

end

【正确代码示例】

always @(*) begin

if( ptype ==ETH_PKT)        

   plen_adj = 4'h4;             

else if( ptype == PPPOA_PKT)

   plen_adj = 4'h8;    

 else

   plen_adj = 4'h0; //必须要有该句

end

规则14:基本电路--避免代码中出现完全相同的组合逻辑

【故障影响】在编码或重构时,容易出现条件遇漏,导致出错。

【解决方法】组合逻辑生成1bit的信号,共用该条件。

【异常代码示例】

always@( posedge clk ) begin

if( ren ==1'b1 && rd_cnt == 3'd4)

   ren_eop <= 1'b1;                

else

   ren_eop <= 1'b0;                

end

always@( posedge clk ) begin

if( ren ==1'b1 && rd_cnt == 3'd4) //两个always出现相同的组合逻辑

   pkt_cnt <= pkt_cnt + 1'b1;     

else;

end

【正确代码示例】

assign rd_over = (ren ==1'b1 && rd_cnt == 3'd4)? 1'b1:1'b0;

always@( posedge clk ) begin

if( rd_over == 1'b1 )

   ren_eop <= 1'b1;                

else

   ren_eop <= 1'b0;                

end

always@( posedge clk ) begin

if( rd_over == 1'b1 )

   pkt_cnt <= pkt_cnt + 1'b1;     

else;

end

规则15:基本电路--FIFO内部读写指针需要上下溢出保护

【故障影响】FIFO内部读写指针不做保护,可能会使FIFO读写溢出以及空满状态出现异常。

【解决方法】FIFO上下溢出时需要保证读写指针值保持不变,做到写入数据丢弃,读出数据不变。

【异常代码示例】

always@( posedge clk_wr or posedge rst_wr) begin

if ( rst_wr == 1'b1 )

   wt_addr <= {(ADD_DEPTH+1){1'b0}};           

else if ( clr_wr_2d == 1'b1)

   wt_addr <= #U_DLY {(ADD_DEPTH+1){1'b0}};

else if ( wr == 1'b1 )

   wr_addr <= #U_DLY wr_addr + 1'b1;                                     

else

   ;

end

【正确代码示例】

always@( posedge clk_wr or posedge rst_wr) begin

if ( rst_wr == 1'b1 )

   wt_addr <= {(ADD_DEPTH+1){1'b0}};           

else if ( clr_wr_2d == 1'b1)

   wt_addr <= #U_DLY {(ADD_DEPTH+1){1'b0}};

else if ((wr == 1'b1) && (full == 1'd0))//需要判断非满

   wr_addr <= #U_DLY wr_addr + 1'b1;                                     

else

   ;

End

规则16:基本电路--写入fifo/RAM数据进行校验

【故障影响】FIFO、RAM的数据未进行校验,在电路失效导致数据出错时可能威胁系统的可靠运行。

【解决方法】需要将FIFO、RAM的数据进行奇偶或ECC校验,并把结果上报CPU,进行异常处理。

【异常代码示例】

sdp_ram

#(

.WRITE_WIDTH(64),

.READ_WIDTH (64)

) u0_ram

(

.data (data_in[63:0]),

.wren (data_ram_wr),

.wraddress (data_wr_addr),

.rdaddress (data_rd_addr),

.clock (clk_sys),

.data_out(data_out[63:0])

);

【正确代码示例】

sdp_ram

#(

.WRITE_WIDTH(66),

.READ_WIDTH (66)

) u0_ram

(

.data (^data_in[63:32],^data_in[31:0],data_in[63:0]),

.wren (data_ram_wr),

.wraddress (data_wr_addr),

.rdaddress (data_rd_addr),

.clock (clk_sys),

.data_out(data_out[65:0])

);

规则17:原子操作

对于原子操作(如SPI、IIC 器件配置等)必须保证每次操作过程中不被打断。并且增加原子操作冲突的后处理机制。在软件设计中,原子操作是指不会被线程调度机制打断的操作。这种操作一旦开始就一直运行到结束,中间不会有任何线程切换。FPGA和ASIC 设计中同样可能存在多个模块访问同一资源的模块或单元,如果也将这些模块或单元视为线程的话,必须由设计保证当某一个线程在操作资源过程中不会和其他线程冲突。这种不可被打断的操作过程即为原子操作。

原子操作常用在SPI、IIC、UART等器件配置:软件将并行的配置值写给逻辑,逻辑将数据串行送出,在串行送出的过程中,软件可能再次给出新的配置值。

示例1:SPI 操作过程本身的原子保护

在xxx 模块中普遍采用VGA(压控增益衰减器)器件进行通道增益控制。系统对功控响应速度要求较高,因此通常会由 FPGA 实现 VGA配置操作。

FPGA内部存在多个功能模块决定VGA 取值,由于多个模块间调度是随机切换的,目标控制值可能在配置 VGA 过程中被更新。此时要求 VGA 器件的配置流程必须作为原子操作进行保护,否则便会引起配置干预。

例如初始配置值为0xAAA,在配置完高 4bit 后目标值被刷新为0xBBB,因为此时配置流程仍在继续故低 8bit 数据使用了新值,最终 SPI配置结果为 0xABB。由此导致实际配置值非目标配置值,可能造成不可预知的影响。

示例2:移位拼接的原子保护

某 RRU功控中,逻辑需要根据 A/B 通道的定时切换指示、或CPU/DSP 发起的配置指示,通过 SPI 接口动态切换反馈本振的频率。反馈本振的频率配置数据为 192bit,由软件提前通过寄存器接口写给逻辑,对应逻辑的 12 个寄存器,每个寄存器位宽为 16bit。逻辑检测到配置指示后,一方面把配置 start 指示传递给 SPI 接口模块,另一方面把 12 个寄存器的值做移位拼接并送给 SPI 接口模块,正常情况下 SPI 接口模块用 start 指示锁存的数据是移位拼接结束后稳定的 192bit。若仅对 SPI 接口本身做原子保护,不对移位拼接做原子保护,在start 指示间隔出现异常抖动时,SPI 接口模块可能锁存到移位过程中的数据,并配置给外部芯片,导致不可预知的结果。该例子中,除了对 SPI 接口本身做原子保护,还需要对与接口相关的外部操作进行原子保护。

哪些场景,需要原子操作冲突的后处理机制?

场景1: 外部发起原子操作的时候不能判断逻辑的 busy 状态,无法感知到是否有冲突发生(注意:如果是软件发起原子操作,则必须对逻辑的 busy 状态做检测);

场景 2:原子操作不是周期性被发起的,一旦发生冲突,冲突所对应的数据没有机会被重新配置。若同时符合以上两个条件,建议对原子操作冲突做后处理机制。

某逻辑版本中,逻辑需要根据外部管脚送来的切换指示把 A/B 通道的反馈衰减值通过SPI 接口配置给外部 DSA 器件,SPI 操作过程有原子保护。外部芯片通过管脚的高低电平变化通知逻辑启动 A/B 通道的 SPI 配置,因此外部无法感知 SPI 的 busy 状态。某些载波配置场景下,外部AISC芯片送来的切换指示仅在建立载波时做一次切换而非周期性切换,若芯片因定时抖动等原因送给逻辑的切换脉冲宽度小于一个 SPI 周期,则导致最后一次要配置的正确数据因原子操作保护而无法配置出去,但芯片并不能感知到这种冲突,导致系统异常无法恢复。该场景下需要考虑原子操作冲突的后处理机制。后处理机制可参考以下方法:

(1)[推荐做法] 逻辑在原子操作结束以后,对最后一次冲突的数据再发起一次操作,对中间冲突的数据忽略。如下图所示,外部组件通过信号跳变沿触发逻辑进行原子操作,逻辑根据外部触发信号产生一个内部的跟随信号,用内部跟随的信号触发原子操作。正常不出现冲突的情况下,内部跟随信号与外部信号保持一致,基本没有延迟,仅在出现冲突时内部跟随信号比外部触发信号有延迟,但延迟低于 1个原子操作周期且仅在出现冲突时才有延迟,该延迟在出现冲突时不可避免。

(2)逻辑对外部发起的原子操作触发信号做过滤处理,保证过滤后的触发指示不会出现原子操作冲突。但该处理会导致原子操作实际发起的时间固定比外部触发的时间有延迟(至少1个原子操作周期),仅适用于对延时不敏感的场景;

(3)软件做后处理。软件针对原子冲突的场景做识别并规避,逻辑不做后处理;

(4)若外部组件发起原子操作时不能对逻辑 busy 状态做判断,则逻辑对发起原子操作的组件提出要求,从源头保证不出现操作冲突。

规则18:自愈设计

自愈是人体和其他生命体在遭遇外来侵害或出现内在变异等危害生命情况下,维持个体存活的一种生命现象,具有自发性、非依赖性和作用持续性等显著特点,自愈是一种稳定和平衡的自我恢复机制。

自愈设计是指系统在遇到异常状况时,依靠事先设计好的机制,维持系统正常工作的设计方法。自愈设计可以由系统中多个组件互相配合实现,而不是由一个组件单独实现。自愈设计的通常步骤:检测故障,如果出现故障则记录日志并进行自愈操作,如果没有出现故障则等待下次检测。注意:第一,即使自愈操作能够使系统恢复,也需要在系统中记录发生了自愈操作,便于系统维护。第二,自愈操作设计本身也要有退出机制,不能进入死循环,例如:PLL 出现失锁,自愈设计对 PLL 进行复位,复位也有可能无效,设计不能死循环一直复位 PLL。自愈设计常见应用场景包括: 移位寄存器、计数器、状态机、FIFO、故障处理等。

(1) 避免反馈式(有记忆)电路没有自愈机制(如依赖复位初始状态才能正常工作的电路)。

[示例]下面是一个通过循环移位寄存器产生串行”0x7E”比特流的典型电路。逻辑在复位的时候将循环移位寄存器的初始值置成 0x7e。正常情况下,循环移位寄存器只会出现 8 个循环移位值:0x7e、0xfc、0xf9、0xf3、0xe7、0xcf、0x9f、0x3f。但当时钟信号出现异常的时候,则寄存器的值被异常修改,出现上述 8 种以外的取值,导致后续的移位输出不是期望的“0x7E”比特流,且异常不可恢复。

为了防止电路进入上述不可恢复状态,我们可以给电路加上一个“看门狗”。在如下代码中,我们通过增加一个reload_en 信号,当移位寄存器的值出现异常的时候重新将其置成初始值,从而保证了逻辑即使在时钟信号发生异常的时候,也能通过自恢复机制恢复成正常状标。

(2)避免信号间的相位或者时序关系依赖于复位初始状态。

[示例]: 如下代码实现占空比为 50%的 5 分频电路,方法是分别用时钟的上下边沿驱动两个模 5计数器,分别产生两个占空比为 40%的 5 分频信号,把两个信号相或就得到一个占空比为 50%的 5分频时钟。该电路实现的代码如下:

在仿真或者正常情况下,上述电路都不会出现问题。但是在时钟发生异常的时候,两个计数器的值就会变得不确定导致两个计数器的相同的计数值在时间上差了 1.5个时钟周期,两个 2:3 的分频信号相或输出就会变成 7:3,导致分频不正确,且无法恢复。解决办法是只采用一个计数器,代码如下:

(3)逻辑工作的关键资源,比如状态机、FPGA的工作模式、模块的工作使能可以利用软件定时核查的方式进行保护,防止异常状态下关键资源被修改导致逻辑挂死。

案例:

问题描述: 逻辑版本加载后,cpri状态机初始态为 State A,fpga驱动启动 cpri状态机之后才开始正常的状态跳转。但单板工作条件异常(如时钟异常等)会导致 fpga 寄存器误翻转,可能导致 cpri状态机由 4(State E)常跳变到 0(State A),因为 fpga 驱动未再次启动 cpri状态协商,导致 cpri链路彻底痪,影响致命。

问题解决: 现有的告警上报机制,fpga驱动会响应秒中断,每隔 1s 读取 fpga 告警寄存器。只需要 fpga 驱动或其他软件功能模块在秒中断处理机制中,增加检测 cpri 状态机的机制,如果检测到 cpri 处于 State A,则再次启动状态机,或者启动 cpri开工流程。

(4)建议寄存器配置核查:系统运行中针对FPGA关键配置信息进行周期性核查,提高系统自愈能力

说明: FPGA配置核查的目的主要是为了校验软件和 FPGA 间配置数据不一致,比如由于某种原因,软件虽然更新了配置数据,但是没有把数据配置给 FPGA。配置核查的周期可以定为小时级或更长,核查的预期结果软件可通过两种方式维护

a.在内存中维护一套配置映射表,核查时将 FPGA 中信息读出后逐一进行比对识别配置异常。

b.将配置核查相关信息进行 CRC 计算后保存计算结果作为基准,在核查时重新计算CRC 并和基准值进行比较,识别配置异常。配置核查发现异常可对 FPGA重新下发配置信息,实现系统自愈。

(5)建议关键寄存器的配置增加配置使能寄存器进行保护

(6)周期性定时同步提高可靠性。

说明: 需要周期性同步的电路,例如 SERDES 需要发送 K28.5 到达接收侧字节边界对齐,避免出现只在上电或者复位只初始化一次的情况,可以通过周期性定时同步的方式提高可靠性。

(7) FIFO 需要支持故障检测、复位等自愈设计,避免出现挂死。

说明: FIFO 容易因为外部干扰,时钟异常等因素导致指针错乱,而进入死锁状态。设计中要支持自愈设计,可以逻辑自身实现故障检测,并进行自复位(内部 FIFO); 也可以由外部软件实现故障检测,并进行复位(外部接口 FIFO)。

(8)PLL/DCM 需要支持失锁检测、重配置复位等自愈设计,避免出现挂死。

说明: PLL/DCM 容易因为外部干扰,时钟异常等因素导致进入失锁状态,需要进行重配置复位才能恢复。设计中要支持自愈设计,可以由外部软件实现故障检测,并进行复位;也可以逻辑自身实现故障检测,并进行自复位。因为软件实现的方案灵活,并且可以方便记录日志。建议使用软件实现 PLL/DCM 类模块的自愈设计。

(9)数据链路支持心跳检查。

[示例]: 为检测单板 CPU 到业务网口之间数据流的正常工作,可以设计心跳报文,CPU定时发出,在业务网口处环回给 CPU,CPU 检测是否定期收到心跳报文,如果没有收到则进行后处理。再如,DSP 定时给 FPGA 发送信号,当FPGA 接收不到此信号时,可判断 DSP挂死,不再向 DSP 发包,并发送告警信号。

接口电路规则

规则1:接口--输入数据SOP/EOP信号异常检测和过滤

【故障影响】对输入数据SOP/EOP异常未进行过滤,可能会导致逻辑处理异常甚至挂死。

【解决方法】接口处部署sop和eop异常检测和过滤器。检测和滤除sop异常包和eop异常包。

【案例】修改对端芯片配置,接收侧业务出现挂死

【正确代码示例】

always @( posedge clk_sys or posedge rst ) begin

if(rst)

sop_flag <= 1’d0;

else if ( sop == 1'd1)

   sop_flag <= 1'd1;                                

else if ( eop == 1'd1 )

   sop_flag <= 1'd0;          

end

//产生包头异常指示

always @( posedge clk_sys or posedge rst) begin

if(rst)

sop_err_ind <= 1d0;

else if ( eop == 1'd1 && sop_flag == 1'd0 )

   sop_err_ind <= 1'd1;

else

   sop_err_ind <= 1d0;

end

//产生包尾异常指示

always @( posedge clk_sys or posedge rst )

if(rst)

 eop_err_ind <= 1'd0;

else if ( sop == 1'd1 && sop_flag == 1'd1)

   eop_err_ind <= 1'd1;

else

   eop_err_ind <= 1'd0;

规则2:接口--接口输入数据长度信息异常保护处理

【故障影响】对输入数据的长度异常未进行保护,可能会导致逻辑处理异常甚至挂死。

【解决方法】接口处部署包长检测和滤除模块,检测和滤除包长异常包、超长、超短包。

【正确代码示例】

always @( posedge clk_sys or posedge rst ) begin

if ( vld == 1'd1 )

   if(sop == 1'd1)

      byte_cnt <= 1'd1;

   else

      else byte cnt <= byte_cnt + 1'd1;        

else

   ;

end

always @( posedge clk_sys or posedge rst ) begin

if ( eop_1dly == 1'd1 && byte_cnt != pktlen)

   pkt_len_err_ind <= 1'd1;

else

    pkt_len_err_ind <= 1'd0;

end

规则3:接口--用户访问地址的合法性需要进行检查

【故障影响】对用户访问地址未进行合法性检查,可能会导致数据误写入非预期的地址,或者访问到非用户预的数据

【解决方法】需要对用户访问地址的合法性进行检查,禁止用户通过非法地址访间逻辑内部数据。对于非法地址访问,逻辑可以返回特定值用以指示访问异常

【异常代码示例】

always@(posedge clk or posedge rst) begin

if (rst == 1'b1)

   cpu_rdata <= 32'b0;

else if ((cpu_req == 1'b1) && (cpu_rw == 1'b1))

   case (cpu_addr[3:0])

      4'h0: cpu_rdata <= cfg_0reg;

          ......

      4'h9: cpu_rdata <= dbg_1reg;

      4'ha: cpu_rdata <= dbg_2reg;

      default: cpu rdata <= cpu_rdata; //访问非法地址,可能读到历史值

   endcase

else

;

end

【正确代码示例】

//非法地址空间检查

assign cpu_haddr_nzero = (cpu_addr[15:4] == 12'b0)?1'b1:1'b0;

always@(posedge clk or posedge rst) begin

if (rst == 1'b1)

   cpu_rdata <= 32'b0;

else if ((cpu_req == 1'b1) && (cpu_rw == 1'b1))

   case (cpu_addr[3:0])

      4'h0: cpu_rdata <= cfg_0reg;

          ......

      4'h9: cpu_rdata <= dbg_1reg;

      4'ha: cpu_rdata <= dbg_2reg;

      default: cpu rdata <= 32’hdeadbeet; //对于非法地址访问逻辑,可以返回特定值用以指示访问异常

   endcase

else

;

end

规则4:接口--逻辑接口关键配置进行非法检查和过滤

【故障影响】对逻辑接口下发的配置未做合法性检查,可能使异常配置下发给逻辑,导致工作异常。

【解决方法】对配置内容进行异常检查和过滤,防止异常配置输入导致系统处于不可预知的状态。

【异常代码示例】

always @( posedge clk or posedge rst) begin

if (rst == 1'b1)

   op_mode <= 3'd0;                        

else if ((pwrite == 1'b1) && (paddr == ADDR_OP_MODE))

   op_mode <= pwdata[2:0];                    

end

【正确代码示例】

always @( posedge clk or posedge rst) begin

if (rst == 1'b1)

   op_mode <= 3'd0;                        

else if ((pwrite == 1'b1) && (paddr == ADDR_OP_MODE) && pwdata[2:0] <= 3'd5)

   op_mode <= pwdata[2:0]; //op_mode小于等于5为合法值。                    

end

规则5:接口--用户访问任意地址时,需要有应答

【故障影响】用户流问非法地址,如果无响应。有可能出现无应答挂死。

【解决方法】用户访问任意地址,包括合法地址和非法地址,逻辑都需要给予响应。

【异常代码示例】

always @(posedge clk or posedge rst) begin

if (cpu_req == 1'b1)

   if (cpu_cs_cfg == 1'b1)             

      cpu_ack <= cpu_ack_cfg;             

   ......                            

   else if (cpu_cs_tbl == 1'b1)

      cpu_ack <= cpu_ack_tbl;

   else; //无效地址时无响应,可能使用户挂死                   

else

   cpu_ack <= 1'b0;

end

【正确代码示例】

always @(posedge clk or posedge rst) begin

if (cpu_req == 1'b1)

   if (cpu_cs_cfg == 1'b1)             

      cpu_ack <= cpu_ack_cfg;             

   ......                            

   else if (cpu_cs_tbl == 1'b1)

      cpu_ack <= cpu_ack_tbl;

   else

      cpu_ack <= 1'b1;

else

   cpu_ack <= 1'b0;

end

规则6:接口--缓存管理接口缓存耗尽检测

【故障影响】未对缓存管理异常进行监控,则在缓存出现泄露时,无法及时处理,威胁到系统的可靠运行。

【解决方法】对缓存管理接口进行异常检测,包括重复分配告警、重复回收告警。

【正确代码示例】

always @( posedge clk_sys or posedge rst ) begin

if(get_addr_vld == 1'd1 && addr_use_flag == 1'd1)

   get_addr_err <= 1'd1;

else

   get_addr_err <= 1'd0;

end

always @( posedge clk_sys or posedge rst ) begin

if(rls_addr_vld == 1'd1 && addr_use_flag == 1'd0)

   rls_addr_err <= 1'd1;

else

   rls_addr_err <= 1'd0;

end

基础安全编码规则

基础安全编码规则主要针对逻辑攻击防护的问题,即攻击者不需要专业设备(例如功耗采集设备、错误注入工具等),通过FPGA已有对外接口漏洞进行攻击。

规则1:安全接口----对寄存器的访问权限需要做控制和隔离

【威胁】如果寄存器权限不做隔离和控制,攻击者可以通过接口任意访问和修改寄存器内容。

【解决方法】安全CPU访问的寄存器(包括配置寄存器、状态寄存器、DFX寄存器等)需要进行权限控制。

【正确代码示例】

安全属性寄存器只有安全CPU可配置

assign sec_data_wr = ((pwrite==1'b1) && (paddr==ADDR_SEC_DATA)&&(cpu_type == SEC_CPU));

always @(posedge clk or posedge rst) begin

(rst == 1'b1)

   sec_data_din <= 32'b0;

else if(sec_data_wr == 1'b1)

   sec_data_din <= pwdata(31:0];

else

   ;

end

assign nsec_data_wr = ((pwrite==1'b1) && (paddr==ADDR_NSEC_DATA));

always @(posedge clk or posedge rst) begin

(rst == 1'b1)

   nsec_data_din <= 32'b0;

else if(nsec_data_wr == 1'b1)

   nsec_data_din <= pwdata(31:0];

else

   ;

end

规则2:安全接口----调试接口在芯片发布后需要限制访问

【威胁】攻击者可以直接通过调试接口读取芯片内的敏感信息和修改芯片的安全配置。

【解决方法】在产品发有布后,必须限制调试接口的访问权限,可以采用鉴权或永久关断方式。

【异常代码示例】

always @( posedge clk or posedge rst) begin

if (rst == 1'b1)

   prdata <= 32'd0;

else if(rd_en == 1'b1) begin

   case(rd_addr)

      ADDR_WORK_EN:  prdata <= {31'd0,work_en);

      ADDR_KEY_CRC:  prdata <= aes_key_crc;

      default: prdata <= 32'd0;

   endcase

   end

end

【正确代码示例】

//宏定义方式删除密钥调试接口

always @( posedge clk or posedge rst) begin

if (rst == 1'b1)

   prdata <= 32'd0;

else if(rd_en == 1'b1) begin

   case(rd_addr)

      ADDR_WORK_EN:  prdata <= {31'd0,work_en);

      //增加宏命令,调试时可读,生产时不可读。

      `ifdef FPGA_DEBUG

      ADDR_KEY_CRC:  prdata <= aes_key_crc;

      `endif

      default: prdata <= 32'd0;

   endcase

   end

end

规则3:敏感信息----禁止通过接口读取

【威胁】攻击者通过该接口获取敏感信息敏感信息指密钥、口令、知识库/规则库。

【解决方法】禁止通过接口读取敏感信息

【异常代码示例】

示例通规则2

【正确代码示例】

示例通规则2

规则4:敏感信息----使用结束后立即清除

【威胁】敏感信息寄存器处于保持状态,存在被攻击者恶意利用或者泄露的风险。

【解决方法】敏感信息寄存器使用结束后清零。

【异常代码示例】

always @( posedge clk or posedge rst ) begin

if (rst == 1'b1)

   password_reg <= 32'hxxxxxxxxx;              

else if (password_input_vld == 1'b1)

   password_reg <= password_input;              

else//口令寄存器没有清零处于保持态,存在泄露风险

   ;

end

【正确代码示例】

always @( posedge clk or posedge rst ) begin

if (rst == 1'b1)

   password_reg <= 32'hxxxxxxxxx;

else if (done == 1'b1)

   password_reg <= 32'hxxxxxxxxx;

else if (password_input_vld == 1'b1)

   password_reg <= password_input;              

else

   ;

End

规则5:敏感信息----用于安全用途的随机数禁止复用

【威胁】随投整信售用的有机需造,不可预知等特性,如果复用随机数则可以根据随机数预知生成的会话秘钥等,导致秘钥泄露。

【解决方法】安全用途随机数禁止复用,不同的安全功能使用不同的随机数。

【异常代码示例】

always @( posedge clk or posedge rst)

if (rand_vld == 1'b1)

   key_reg <= random[31:0];

else

   ;

end

always @( posedge clk or posedge rst) begin

if (rand_vld == 1'b1)

   nonce <= random[31:0]; 

else

   ;

end

//nonce和key复用随机数,有泄露风险

【正确代码示例】

always @( posedge clk or posedge rst ) begin

if ((rand_vld == 1'b1) &&(rnd_cnt == 2'b0))

   key_reg <= random[31:0];

else

   ;

end

always @( posedge clk or posedge rst ) beqin

if ((rand_vld == 1'b1) &&(rnd_cnt == 2'b1))

   nonce <= random[31:0]; //nonce和key使用不同时刻随机数

else ;

end

规则6:敏感信息----初始值禁用特殊值

【威胁】攻击者通过尝试几种特殊值即可猜测出芯片敏感信息初值,从而绕过安全机制。

【解决方法】可以使用随机无规律的静态值作为初始值。

【异常代码示例】

always @( posedge clk or posedge rst )

if (rst == 1’b1)

   aes_key_reg <= 128'h0; //全0

else if (key_vld == 1'b1)

   aes_key_reg <= aes_key_in;        

else

   ;

end

【正确代码示例】

always @( posedge clk or posedge rst )

if (rst == 1’b1)

   aes_key_reg <= 128'h4dfea279_4fb9_25da_6223_18ea_5ff2_986a; //随机的静态值

else if (key_vld == 1'b1)

   aes_key_reg <= aes_key_in;        

else

   ;

end

高级安全编码规则

高级安全编码规则主要是针对有密码算法运算且密码算法有硬件三防要求的FPGA。解决防故障注入攻击和防侧信道攻击的问题。

故障注入攻击防护技术

表xx 故障注入攻击防护技术

防护技术

技术概述

sensor

对电压、时钟进行异常检测和告警

CRC检测算法

对敏感数据,计算CRC校验值,如果校验值不对,则上报告警

逻辑&时间冗余

分为面积冗余和时间冗余

采用安全编码后,故障注入攻击需要同时实现状态机的4bit翻转,攻击难度变大。

规则1:电路----关键控制寄存器必须冗余保护

【威胁】不做冗余保护,故障注入的攻击难度低。

【解决方法】关键寄存器采用冗余保护,增大故障注入篡改寄存器的难度。

【异常代码示例】

always @( posedge clk or posedge rst )

if((pwrite == 1'b1) && (paddr == SEC_CFG ADDR)) begin

   sec_cfg_1bw <= 1'b1;  

else

   ;

end

assign cfg_vld = sec_cfg_1bw; //单bit信号,故障注入攻击难度低

【正确代码示例】

always @( posedge clk or posedge rst )

if((pwrite == 1'b1) && (paddr == SEC_CFG ADDR)) begin

   sec_cfg_4bw <= 4'd5;  

else

   ;

end

assign cfg_vld = (sec_cfg_1bw==4'd5); //单bit信号,故障注入攻击成功的概率只有1/16

【其他】冗余关键控制寄存器非法值检查,实现攻击状态可检测,上报CPU异常处理

规则2:电路----关键比较器采用两种以上的独立电路实现

【威胁】关键比较器只有一种电路实现,故障注入攻击难度低。

【解决方法】关键比较器采用两种或者两种以上的独立电路实现。

【异常代码示例】

assign cpm_ok=(dina == dinab);

【正确代码示例】

//相等比较电路

CMP_EQUAL U_CMP_EQUAL(

.dina(dina),

.dinb (dinb),

.dout(cpm_equal)

);

//异或比较电路,采用模块化实现,便于防优化约束

CMP_XOR U_CMP_XOR(

.dina(dinb),

.dinb (dina ),

.dout (cpm_xor )

);

//两种比较必须都成功

assign cmp_ok = cpm_equal & cpm_xor;

规则3:电路----关键比较器采用两次及以上的电路实现。

【威胁】关键关键比较器只比较一次,故障注入攻击难度低。

【解决方法】关键比较器采用两次或者两次以上的电路实现。

【异常代码示例】

assign cpm_ok=(dina == dinab);

【正确代码示例】

always @(posedge clk or posedge rst) begin

if (rst == 1'b1)

   cmp_flag <= 2'b0;

else if (cmp_flag == 2'b11)

   cmp_flag <= 2'b0;

else if (cmp1_ok == 1'b1)

   cmp_flag <= {cmp_flag[1],1'b1};

else if (cmp2_ok == 1'b1)

   cmp_flag <={1'b1,cmp_flag[0]}

end

//两次比较必须都成功

assign cmp_ok = (cmp_flag == 2'b11);

规则4:电路----状态编码汉明距离大于等于2

【威胁】如果状态机编码汉明距离小于2容易被故障注入攻击、篡改状态。

【解决方法】状态机编码时需要将汉明距离设置大于2

【异常代码示例】

parameter SEC_FSM_OPEN = 2'b01;

parameter SEC_FSM_LOCK = 2'b10;

parameter SEC_FSM_CLOSED = 2'b11;

【正确代码示例】

parameter SEC_FSM_OPEN = 4'h5;

parameter SEC_FSM_LOCK = 4'ha;

parameter SEC_FSM_CLOSED = 4'h9;

侧信道攻击防护方法

侧信道攻击通过采样工作过程中密码泄露出来的功耗曲线恢复秘钥。如下图,由于秘钥相关寄存器在赋值时,会有0->1或者1->0变化,寄存器的0/1变化,对外表现为动态功耗的变化。

加掩防侧信道攻击,通过加掩去除输入数据与侧信道相关性,增大侧信道攻击的难度。

规则1:密码----密码运算前添加掩码

【威胁】秘钥密码算法在运算前不加掩码,会在运算过程中泄露密钥的功耗、电磁信息,存在被侧信道分析方法恢复出秘钥的风险。

【解决方法】密码算法运算前添加掩码。

【正确代码示例】

//敏感信息使用前添加随机数掩码

SEC_XOR U0_SEC_XOR(

.dina(key_in),

.dinb(random),

.dout(key_in_mask)

);

//异或功能

SEC_XOR U1_SEC_XOR(

.dina(key_in_mask),

.dinb(xor_in),

.dout(xor_rslt_mask)

);

//去掉掩码

SEC_XOR U2_SEC_XOR(

.dina(xor_rslt_mask),

.dinb(random),

.dout(xor_rslt)

);

**规则2:密码----有顺序要求运算逻辑采用模块例化方式 **

【威胁】不采用模块方式,则存在被综合工具优化掉安全编码的风险。

【解决方法】有顺序要求的运算逻辑采用模块化方式,先加掩码再去旧掩码,避免明文泄露。

【错误代码示例】

assign key_new_mask = key_mask^new_mask^old_mask;

【正确代码示例】

//敏感信息使用前添加随机数掩码

SEC_XOR U0_SEC_XOR(

.dina(key_in),

.dinb(new_random),

.dout(key_in_mask)

);

//异或功能

SEC_XOR U1_SEC_XOR(

.dina(key_in_mask),

.dinb(old_random),

.dout(xor_rslt_mask)

);

规则3:密码---关键信号命名添加安全前缀和位宽

【威胁】信号存在被综合工具优化掉安全编码的风险,导致安全编码失效。

【解决方法】关键信号加安全前缀和位宽,如”sec_”,”_4bw”,然后根据名字添加防止被优化的约束,也便于后期检查网表。

【错误代码示例】

reg (7:0] dbg_dis; //debug disable信号定义

reg [3:0] core_rob_alarm;//core模块的鲁棒性告警信号定义

【正确代码示例】

//便于对信号设置约束,便于通过工具检查信号的编码实现是否符合预期

reg [7:0] sec_dbg_dis_8bw; //debug disable信号

reg [3:0] sec_core_rob_alarm_4bw; //core模块的鲁棒性告警信号定义

关键参数添加关键字"sec",目的是便于通过工具检查定义参数的汉明距离是否足够;

关键模块添加关键字"sec”目的是防止工具优化掉模块,导致安全编码防攻击措施失效

规则4:安全约束----关键寄存器设置防优化约束

【威胁】综合工具可能将寄存器/信号优化为不符合预期的电路实现,导致安全编码失效。

【正确方法】设置防优化约束,避免综合工具把冗余编码优化掉。

【正确代码示例】

(* dont_touch="true" *) reg [3:0] sec bus_disable_4bw;

(* dont_touch="true" *) reg sec_cfg_1bw;

(* dont_touch="true" *) reg sec_cfg_1bw_dup;

(dont_touch="true") SEC XOR U0_SEC_XOR(

.dina(key_mask),

.dinb(new_mask),

.dout(key_mask_new_mask)

);

针对功耗攻击的防护措施

  1. 在芯片的电源中引入噪声。增加电源中的噪声能够降低攻击者采集到的功耗曲线的信噪比,提高功耗分析难度。
  2. 增加去耦电容。在电源VDD和GND之间增加去耦电容能够平滑攻击者采集到的功耗曲线,同样降低了其信噪比,提高分析难度。
  3. 使用随机时钟。随机时钟使得加密模块能够随机化地间断执行,让攻击者每次采集到的功耗曲线都存在时间上的漂移,从而攻击者难以将所有功耗曲线的被攻击时刻点对齐以完成后续的功耗分析。
  4. 随机插入伪操作。相对于加密模块的正常操作,伪操作的随机插入同样让攻击者每次采集到的功耗曲线都存在时间上的漂移,从而攻击者难以将所有功耗曲线的被攻击时刻点对齐以完成后续的功耗分析。
  5. 采用掩码技术。掩码技术使得加密模块实际运算的输入数据和原始数据不同,实际运算的输入数据为原始数据经过掩码操作后的数据,攻击者由于难以得知掩码操作后的数据,故难以通过加密模块的功耗模型得到正确的理论功耗。

以上五条防护措施只是所有可能的防护措施的一部分,也存在其它有效的针对功耗攻击的防护措施,在实际工程中常常同时使用多种防护措施以提高攻击难度。

FPGA逻辑军规

该逻辑军规主要适用于FPGA开发,目标是消除非技术能力原因造成的低级错误。

逻辑军规第一条:FIFO和状态机

内容

FIFO接口、状态机需要有异常自恢复和状态上报机制,格雷码电路需要有防止被综合工具错误优化掉的约束。

违反军规的后果
  1. 缺少异常保护机制,FIFO、状态机在异常后不能自恢复,导致致命故障。
  2. 缺少状态上报机制,与FIFO、状态机相关模块功能异常无法准确定位,不能为故障定位提供信息,导致定位效率低。
  3. 缺少防止格雷码被综合工具优化的约束,将直接导致异步FIFO功能性缺失,导致致命故障。
解析

这里特地增加了“自“恢复,即强调逻辑因链路突发性异常出现故障后,在链路恢复正常时,能够自恢复。而自恢复最常用的手段,就是通过系统同步信号定期地将逻辑状态恢复到复位时的状态,其它自恢复设计多与应用场景相关,比如异步FIFO,需要判断是否出现既空又满而挂死,在此时自动复位FIFO,并上报软件。

fifo接口需有异常恢复和状态上报机制,在读写指针异常时可以自动复位,并对异常情况进行统计上报。对于有严格时延要求的fifo接口需要提供指针复位控制信号。提供FIFO空、满(半空、半满标志可选)标志寄存器,要求这些标志软件可以访问。自行设计的FIFO中格雷码电路防止被综合工具错误地优化掉:Synplify综合格雷码电路会将一些寄存器优化掉,出现错误。 解决方法:在格雷码相关寄存器上加综合引导语句“synthesis syn_preserve= 1”,防止优化。

状态机状态引入CPU接口寄存器中,状态机要有default状态,状态机增加安全模式的综合指示或者异常状态保护电路。。

思考:

1、在什么时候使用状态机?

状态机的优势:

对于同一个设计,利用状态机实现可以节省更多的脑细胞;

对于某些设计,不使用状态机即使你消耗再多的脑细胞也不一定能搞定;

一个好的状态机控制电路,总是具有更好的可读性,也便于维护。

状态机的劣势:

对于同一个设计,状态机实现总会消耗更多的LE;

一个不好的状态控制电路,在输入异常时容易栓锁,不能自恢复;

一个不好的状态控制电路,有时会更难读懂,更难维护。

回到问题上来,传统意义上的时序电路一般是顺序方式控制执行的,如果需要插入不可预测的等待时间,传统意义的时序控制电路是不灵活的。而状态机则能克服这个缺点,最常见的复杂调度算法,一般会使用状态机来设计。

2、状态机如何设计?

根据功能需求抽象并画出完整的状态跳转图,进行状态化简,为每个状态设计异常跳转到安全状态(多为IDLE),确保状态机异常后能自恢复。

Moore型状态机可以使用两段式描述方式,Meely型状态机一律使用三段式描述,且复杂跳转必须加以明确注释。

当状态机状态较多或状态跳转控制复杂时,如果状态机工作时钟频率较高,综合选项中约定状态机编码为One-Hot编码,以面积换速度,避免状态机进入中间非法状态。状态机状态上报只能定位状态机异常栓锁或进入死循环,必要时需要将跳转控制信号、甚至状态机输出选择通过特殊方式上报,检测状态机跳转。

逻辑军规第二条:存储单元初始化

内容

电路中所有寄存器、状态机、计数器、FIFO在单板上电复位以及使用前应处于已知的状态。(FPGA中所有存储单元在工作前,应在CPU复位FPGA后,处于一个合法状态)

违反军规的后果
  1. 寄存器未有效初始化,可能导致输出管脚处于非法状态,导致致命故障。
  2. 状态机未有效初始化,可能导致状态机进入非法中间状态、栓锁或者死循环,并可能导致输出管脚处于非法状态,导致致命故障。
  3. 计数器未有效初始化,可能导致输出管脚处于非法状态,导致致命故障。
  4. FIFO未有效初始化,可能导致FIFO无法正常工作,进入栓锁状态,导致致命故障。
解析

计数器设计缺乏鲁棒性,如当计数器小于等于某值时计数,并等于某值时重新计数。如果初始值大于该数,则计数器无法工作。

说明:在设计时应尽量保证有一全局复位信号,或保证触发器、计数器在使用前已经正确清零和状态机处于确知的状态。

寄存器的清除和置位信号,对竞争条件和冒险也非常敏感。在设计时,应尽量直接从器件的专用引脚驱动。另外,要考虑到有些器件上电时,触发器处于一种不确定的状态,系统设计时应加入全局复位/Reset。这样主复位引脚就可以给设计中的每一个触发器输入清除或置位信号,保证系统处于一个确定的初始状态。需要注意的一点是:不要对寄存器的置位和清除端同时施加不同信号产生的控制,因为如果出现两个信号同时有效的意外情况,会使寄存器进入不定状态。

对于状态机设计,由于有可能存在一些状态对于系统而言是“非法的”(或称“无关的”),所以除了在状态机设计时充分考虑各种可能出现的状态,以及一旦进入“非法”状态后可以强迫状态机在下一个时钟周期内进入“合法”状态(一般是初始状态)外,一定要保证系统初始化时状态机就处于“合法”的初始状态,这里最好的办法仍然是使用复位信号强迫状态机进入已知的“合法”状态。如果复位信号是通过CPU的GPIO管脚输入的,则滤毛刺电路可以在CPU_CLK统一处理,其他时钟域仅做同步即可。

存储单元如何初始化?

存储单元(不包含BRAM/FIFO)必须使用复位信号有效初始化,或清零或置位。有效初始化的关键是复位电路的可靠性,后面有章节重点讲解复位设计。那么FPGA的BRAM和FIFO如何初始化呢?

  1. BRAM中的存储单元会在config_done后统一由GSR初始化到零,如果BRAM有指定初始化文件,则会按照指定文件内容初始化。
  2. FIFO的初始化会将读写指针初始化到零,且空有效,满无效。如果在FIFO工作过程中复位FIFO,FIFO中存储的内容并不会被清零。

逻辑军规第三条:异步信号同步处理

内容

跨时钟域以及异步信号必须同步化处理,是nLint、Spyglass完成代码检查。(异步信号使用前,必须先经过同步化处理,并使用Spyglass完成跨时钟域信号处理检查。)

违反军规的后果
  1. 控制信号跨时钟域未有效同步化,后级电路将出现亚稳态,导致致命故障。
  2. 数据信号跨时钟域未有效同步化,数据链路紊乱,导致数据链路致命故障。
解析

尽可能在整个设计中只使用一个主时钟,同时只使用同一个时钟沿,主时钟走全局时钟网络。在FPGA设计中,推荐所有输入,输出信号均应通过寄存器寄存。

当全部电路不能用同步电路思想设计时,即需要多个时钟来实现,则可以将全部电路分成若干局部同步电路(尽量以同一个时钟为一个模块),局部同步电路之间接口当作异步接口考虑,后续章节会重点讲解异步信号同步化处理。

电路的实际最高工作频率不应大于理论最高工作频率,留有设计余量,保证芯片可靠工作。

对于设计中的异步电路,要给出不能转换为同步设计的原因,并对该部分异步电路的工作可靠性(如时钟等信号上是否有毛刺,建立/保持时间是否满足要求等) 作出分析判断,提供分析报告。

关于全局时钟的约束,能上全局的全部上全局,不能上全局的建议通过区域时钟约束,逻辑锁定等保证性能。

代码编写完成后,要使用nLint、Spyglass进行代码检查,并对检查结果进行分析,完成error、warning清零,info确认。"

逻辑军规第四条:时钟电路

内容

电路中不能出现门控时钟、行波时钟、异步的时钟选择电路。

违反军规的后果
  1. 门控时钟、异步的时钟选择电路处理不当会出现毛刺,其所驱动电路会出现逻辑功能性错误,导致致命故障。
  2. 行波时钟与其源时钟如果存在数据传递,实际上等于两个异步时钟域数据传递,处理不当会出现数据传递错误,导致致命故障。
解析

不能使用组合逻辑时钟或门控时钟,组合逻辑很容易产生毛刺,用组合逻辑的输出作为时钟很容易使系统产生误动作,应当尽量避免门控时钟的出现,此时尽可能的将需要用做门控时钟的控制信号综合到数据端,比如时钟使能CE端等。

不能使用行波时钟,尽量避免采用多个时钟,多使用触发器的使能端来解决。

逻辑军规第五条:优先使用专用资源

内容

优先使用器件专用管脚、专用资源实现电路功能。

违反军规的后果

未使用或错误使用专用管脚资源,可能导致单板某些功能难以实现甚至无法实现,导致硬件改板。

解析

FPGA一般都提供了一些专用资源,从管脚上来说有全局时钟管脚、全局复位管脚等,从芯片内来说主要有全局时钟网络等,在电路设计时要尽量利用这些资源,可以更好实现电路设计。

逻辑军规第六条:约束逻辑未使用/驱动管脚

内容

逻辑暂时未使用的但有硬件连线的管脚,逻辑必须显式声明并处理。

违反军规的后果
  1. 当逻辑暂未使用管脚输入信号未引入CPU接口作为只读寄出去,过不了nLint检查;
  2. 当逻辑暂未驱动的管脚未约束可能导致外部信号冲突,单板联调时出现硬件故障。
解析
  1. 输入信号引入CPU接口作为只读寄存器;
  2. 输出信号根据外部的需求可以连接到电源或地;
  3. 输入输出信号根据外部需要可参考输入、输出信号方式处理;
  4. 必须在管脚约束文件中定义,并且在逻辑内部增加该类信号的负载,并确认布线版本中,这些信号没有被优化掉。
  5. 最简洁的处理方式:逻辑暂未使用的管脚,无论外部有无连线,编译选项中全部选择作为输入或输入高阻处理。

逻辑军规第七条:约束设计与时序分析

内容

所有约束(包含外部接口的管脚约束、时序约束,内部约束)是否都正确添加,布线报告满足约束要求,时序无error告警。

违反军规的后果
  1. 约束设计问题一般在温循前都能暴露出来,给联调/测试增加低级故障,影响逻辑团队声誉,严重时影响项目进度;
  2. 时序分析有critical warning而强行发布版本,不出现故障苟且偷生,出现故障生不如死。

逻辑军规第八条:warning信息确认

内容

所有编译、布线、时序分析等过程中的warning和info信息是否都已确认。。

违反军规的后果

忽略现在warning,影响故障定位效率和版本验证更新,严重时可能“压下葫芦起了瓢”

解析

虽然这条军规可执行,但是如果执行力度一般,可能会被忽略。现在逻辑版本动辄几百甚至几千个warning,每次从这些warning mountain warning sea中寻找现在warning无异于大海捞针。而从跟不上解决问题,还是要规范我们的代码设计,对于可以消除的设计warning应该在设计阶段清零,而不应该等到版本形成后再想规避/补救,如warning清零工程。


本文转载自: https://blog.csdn.net/u013056038/article/details/139203160
版权归原作者 冬 卉 所有, 如有侵权,请联系我们删除。

“逻辑这回事(二)----FPGA安全编码规范”的评论:

还没有评论