编程者的不良习惯或疏忽会导致程序存在安全隐患,容易被攻击者攻击。下面说明编程中常遇到的需要注意的安全问题。
(1)缓冲区溢出
缓冲区溢出包括栈溢出、堆溢出、数组下标错误、格式字符串错误、Unicode和ANSI缓冲区大小不匹配等,共同的特点是数据超出了缓冲区的大小,产生缓冲区溢出的主要原因是编程者的疏忽或不良编程习惯。因此,对缓冲区进行数据操作时,编程应时刻注意数据的大小是否超出缓冲区的范围。如:应注意字符串是否已加上截止符、数据拷贝或填写时是否超长、数组的下标计算是否出错;C语言使用安全函数calloc()、strncpy、strncmp、_snprintf(),而尽量不要使用函数malloc()、strcpy()、strcmp()、sprintf()。C++、Qt等面向对象语言的库提供的字符串类解决了字符串操作导致缓冲区溢出的问题。
(2)管理内存中的秘密
维护内存中的秘密数据原则是:获取秘密数据明文到内存后,应立即使用它,用完后立即删除它。在获取秘密数据和刷新内存之间,用于保存数据的时间应尽可能地短,减少秘密数据从内存通过Linux内核换页机制换出到页面文件的可能性。
由于清除内存中数据是个附加操作,有的编译器在编译优化时会删除这个操作代码,因为编译器认为没必要使用代码清除内存中不必要的数据。为了达到清除内存中秘密数据的目的,应关闭编译优化。方法如下:
#pragma optimize(“”, off) memset(pointer, 0, sizeof(pointer); #pragma optimize(“”, on)
如果长期使用内存中的秘密数据,应该将其进行加密或者加锁。但锁定内存会阻止操作系统执行有效的内存管理任务。
(3)检查输入的合法性
每个应用程序都有信任边界,信任边界指应用程序将边界以内的数据认为是安全良好的,一旦数据进入的信任边界,就不会再检查它的合法性了。
每个应用程序在信任边界应该寻找合法的数据,拒绝其他数据。对输入数据最好用正则表达式进行严格的检查。
(4)限制应用程序的信息
关闭应用程序的调试和跟踪功能,不让攻击者获取调试信息。
程序代码中的出错保护语句不要给攻击者提供太多的敏感信息。
(5)程序代码与数据分离
尽量不要将数据放在程序代码中,否则,很容易被攻击者从二进制应用程序中读出。
(6)连接网络数据库
使用最小特权帐户访问数据库;严格限制合法的输入;查询数据库不能用字符串串联创建查询语句,而应使用查询语句的参数化形式;不管代码是否失败,都应关闭对数据库的连接;发生错误时,程序不能打印敏感消息。
(7)socket编程安全
使用socket编程时,在一个应用程序中用一个连接进行工作,不要在应用程序层数据中嵌入主机IP地址;基于连接的协议对安全更有利;让客户端和服务器应用所使用的端口能被用户配置。
(8)程序加密的注意的问题
1)加密用的随机密码需要使用专用的随机密码产生函数来产生,而不能使用普通的随机函数,因为普通随机函数产生的随机数有可预测性。
2)对输入的口令检查有效位长度、随机性和复杂性。应强制口令定期更换。
3)密钥尽量放在靠近加密和解密数据的地方,因为经常移动存储位置的高"机动性"秘密只能在很短的时间内保密。
4)尽量不要在函数中传递密钥明文数据,而是传递一个请求口令的句柄,由句柄所给的函数从固定存储中读出口令。
5 线程并行模型
计算机CPU多核化是当前CPU发展的趋势,应用程序多线程同时在多个CPU上并行运行,可加倍提高程序运行速度。
对于Linux内核来说,线程是轻量级进程,在内核空间,线程和进程统一当作线程看待。
对于应用程序来说,多线程在同一个应用程序的进程空间,共享进程的全局数据,线程还有自己的线程数据,线程数据是线程独有的。由于进程的全局数据可被应用程序的所有线程访问,因此,必须使用线程互斥机制防止线程访问进程全局数据的竞争,即解决线程安全问题。
应用程序的多线程并行运行,并行引起了同步的问题。同步是线程协调访问共享资源的一种机制。常用的同步机制有互斥量、条件变量、信号量、事件、消息队列等。
线程的生命周期包括就绪、运行、阻塞和终止。
线程利用并发机制提高了程序运行速度,但增加了编程的复杂度和线程切换的开销。线程常用于计算密集型应用和I/O密集型应用。计算密集型应用将计算分解为多个线程在多个CPU上运行,提高计算速度。I/O密集型应用利用多多程同时等待不同的I/O操作,例如:服务器响应多个客户的请求。这些模型可以混合运用。下面分别说明这些编程模型。
(1)流水线方式
流水线(pipeline)方式是每个线程重复地执行同一种操作,几个线程协作完成全程操作,数据流串行地被一组线程顺序处理。4级流水线线程工作模型如图4所示。
流水线方式编程思路说明如下:
1)流水级结构
用流水线的流水级结构描述每一级流水线的状态、传递数据、等待条件等特征数据,流水级结构代表一级流水线或说一个线程。流水级样例结构stage_t列出如下:
typedef struct stage_tag { pthread_mutex_t mutex; //保护数据的互斥锁 pthread_cond_t avail; //数据可用 pthread_cond_t ready; //准备数据 int data_ready; //表示数据准备好 long data; //将处理的数据 pthread_t thread; //本流水级线程 struct stage_tag *next; //下一线流水级 } stage_t
2)流水线结构
用流水线结构描述整个流水线的特征数据,流水线结构包括流水线的级数、激活的流水线级数、第一级和最后一级流水级等。这两个数据结构是所有线程共享的,结构需要含有线程互斥变量成员。流水线样例结构pipe_t列出如下:
typedef struct pipe_tag { pthread_mutex_t mutex; //互斥锁 stage_t *head; //第一级流水级 stage_t *tail; //最后一级流水级 int stages; //流水级的级数 int active; //流水级激活的级数 } pipe_t
3)流水级工作方式
每个流水级线程运行在一个死循环,每个循环的操作为:等待上一级的数据准备好,在上一级数据准备好后,进行本流水级数据处理,本流水级数据处理好后,设置流水级结构,如:状态、处理好的数据。使用线程通知函数pthread_cond_signal通知下一级流水级线程。
(2)工作组
工作组模式是一组线程分别独立地处理工作任务,但一组线程共同完成相同的使命(如:处理队列内的请求)。含有3个工作线程的工作组模式如图15。
工作组模式最常见形式是工作队列和线程池,工作队列是一组线程从同一个队列中接受工作请求,线程池是预分配一定数量的线程,以减少线程分配所占用的时间。工作队列和线程池常结合在一起使用。工作队列是Linux内核广泛使用的线程方式。
工作队列将用户的操作函数(称为工作)组成工作队列链表,线程池中一组线程分别从队列中取出工作,并完成工作对应的操作函数,工作完成后,将线程归还线程池。
(3)客户端/服务器模式
客户端/服务器模式是客户端输入请求,服务器完成服务器的请求,并将结果返回给客户端处理。如:主线程或其他客户端线程等待服务器线程完成操作。客户端/服务器模式如图17。