金山卫士开源了,参见金山卫士开源计划。 抱着学习研究的目的下了一份看看。看了一些代码,觉得被忽悠了。中国知名通用软件厂商,民族软件业的一面旗帜就这代码水平?代码显然达不到工业级的标准,只能算是实习生练手的水准。为了给有意拿这份代码当学习资料的初学者提个醒,不被误导,做出了一个艰难的决定,写博文来评论金山安全卫士的代码。
先说说代码中的几个突出问题
- C++的应用不过关。该用const和static的时候不用
- 代码封装做的不好,调用者知道被调用者很多细节,且对被调用者做了过多假设。
- 文件和函数命名不规划。不能表达内容,且容易引起误解
- 测试靠打印而不是assert,很难自动化验证。且测试代码未与工程代码分离。
- 太多的if-else而不会用表驱动
- 函数逻辑不严格,有明显漏洞。
一点一点的看
1 C++的应用不过关。该用const和static的时候不用
ppro\PrivacyProtection\rule\IRule.h
class
IRule
{
public
:
//
MichaelPeng: Name函数可以设置为const
virtual
LPCTSTR Name(
void
)
=
0
;
virtual
void
Enable(BOOL bEnable){}
//
MichaelPeng: Match从语义上也应为const,且参数pData也应为const
virtual
BOOL Match(KAccessData
*
pData)
=
0
;
//
MichaelPeng: DbgPrint从语义上也应为const
virtual
void
DbgPrint(
void
)
=
0
;
};
2 代码封装做的不好,调用者知道被调用者很多细节,且对被调用者做了过多假设。
ppro\PrivacyProtection\rule\KDirRelateRule.cpp
BOOL KDirRelateRule::Match(KAccessData* pData)
{
BOOL bRetCode
=
FALSE;
std::map
<
CString, std::vector
<
CString
>>
::iterator iter;
for
(iter
=
m_mapDirRelates.begin(); iter
!=
m_mapDirRelates.end(); iter
++
)
{
const
CString
&
strDir
=
iter
->
first;
//
MicahelPeng: 在向m_mapDirRelated中插入数据时都调用MakeLower转化成了小写,但在比较时却不转化,假定调用者作了转化??
if
(strDir.GetLength()
<=
pData
->
nFileDirLen
&&
memcmp((LPCTSTR)strDir, pData
->
szFilePath, strDir.GetLength()
*
sizeof
(TCHAR))
==
0
)
{
std::vector
<
CString
>
::iterator iterRelate;
std::vector
<
CString
>&
vecStr
=
iter
->
second;
for
(iterRelate
=
vecStr.begin(); iterRelate
!=
vecStr.end();
++
iterRelate)
{
int
nMemSize
=
pData
->
nProcPathLen
*
sizeof
(TCHAR);
CString
&
strTemp
=
*
iterRelate;
if
(strTemp.GetLength()
==
pData
->
nProcPathLen
&&
memcmp((LPCTSTR)strTemp, pData
->
szProcPath, nMemSize)
==
0
)
{
return
TRUE;
}
}
}
}
//
MichaelPeng:szProcPath应当类似于C:\windows\notepad.exe, 这里需要保证nProcPathLen和nProcDirLen都被正确设置,
//
最好是KAccessData提供方法SetProcPath,在其中将szProcPath, nProcPathLen, nProcDirLen均设置了
//
但没有这种函数,需要靠调用者去保证每次都将这三个变量同时正确设置j
int
nProcNameLen
=
pData
->
nProcPathLen
-
pData
->
nProcDirLen;
LPCTSTR pProcStr
=
pData
->
szProcPath
+
pData
->
nProcDirLen;
//
MicaelPeng: 遍历的容器都一样,没有必要声明两个iterator
std::map
<
CString, std::vector
<
CString
>>
::iterator iter2;
//
...
}
3 文件和函数命名不规划。不能表达内容,且容易引起误解
RuleManager.cpp,你看到这个文件名第一反应这个文件 是做什么用的?管理rule的是吧,接下来看到的代码会超越你所有的常识。
//
KRuleManager.cpp : Defines the entry point for the console application.
//
#include
"
stdafx.h
"
#include
"
KFileExtensionRule.h
"
#include
"
KProcFileRule.h
"
#include
"
KFileDirRule.h
"
#include
"
KFilePathRule.h
"
#include
"
KFileExtRelateRule.h
"
#include
"
KFileNameRelateRule.h
"
#include
"
KDirRelateRule.h
"
#include
"
KProcPathRule.h
"
#include
"
KSignDataRule.h
"
#include
"
KVariableString.h
"
#include
"
KRuleConfig.h
"
#include
"
KTree.h
"
#include
"
KRuleImpl.h
"
#include
"
KRuleTestImpl.h
"
#include
"
signverify.h
"
#include
"
KSignCache.h
"
#include
"
KProcNameCache.h
"
void
TestFileExtensionRule(KAccessData
*
pData)
{
KFileExtensionRule rule;
rule.AddFileExt(_T(
"
.jpg
"
));
rule.AddFileExt(_T(
"
.html
"
));
rule.AddFileExt(_T(
"
.dll
"
));
rule.AddFileExt(_T(
"
.html
"
));
rule.AddFileExt(_T(
"
.DLL
"
));
rule.RemoveFileExt(_T(
"
.dll
"
));
BOOL bRetCode
=
rule.Match(pData);
//
MichaelPeng: 通过打印而非Assert来校验测试结果,不可重复和自动化
DPrintA(
"
KFileExtensionRule::Match return:%d\n
"
, bRetCode);
}
void
TestTree(
void
)
{
KTree
<
int
>
tree;
tree.SetValue(
0
);
tree.AddLeft(
1
);
tree.AddRight(
2
);
//
MichaelPeng: 这里测了啥?没有抛异常就OK了???
}
void
TestRule(
void
)
{
.......
//
MichaelPeng: 这么多KAccessData设置的雷同代码,为何不放到一个数组中用表驱动实现
{
KAccessData data;
//
MichaelPeng: 拷贝字符串和设置长度可放到KAccessData的成员函数中一次完成
//
代码冗余,暴露给外界太多信息,且这里也未设置nProdDirLen,与BOOL KDirRelateRule::Match(KAccessData* pData)的要求矛盾
_tcscpy(data.szProcPath, _T(
"
d:\\a\\b.exe
"
));
_tcscpy(data.szFilePath, _T(
"
c:\\a\\b\\b.doc
"
));
data.nProcPathLen
=
_tcslen(data.szProcPath);
data.nFilePathLen
=
_tcslen(data.szFilePath);
TestKDirRelateRule(
&
data);
}
{
KAccessData data;
_tcscpy(data.szProcPath, _T(
"
c:\\a\\b\\e.exe
"
));
_tcscpy(data.szFilePath, _T(
"
c:\\a\\b\\b.doc
"
));
data.nProcPathLen
=
_tcslen(data.szProcPath);
data.nFilePathLen
=
_tcslen(data.szFilePath);
TestKProcPathRule(
&
data);
}
{
KAccessData data;
_tcscpy(data.szProcPath, _T(
"
c:\\WINDOWS\\system32\\notepad.exe
"
));
_tcscpy(data.szFilePath, _T(
"
c:\\a\\b\\b.doc
"
));
data.nProcPathLen
=
_tcslen(data.szProcPath);
data.nFilePathLen
=
_tcslen(data.szFilePath);
TestKSignDataRule(
&
data);
}
{
KAccessData data;
_tcscpy(data.szProcPath, _T(
"
c:\\Program Files\\Common Files\\Kingsoft\\kiscommon\\kisuisp.exe
"
));
_tcscpy(data.szFilePath, _T(
"
c:\\a\\b\\b.doc
"
));
data.nProcPathLen
=
_tcslen(data.szProcPath);
data.nFilePathLen
=
_tcslen(data.szFilePath);
TestKSignDataRule(
&
data);
}
TestKVariableString();
TestCreateRules();
TestTree();
}
int
_tmain(
int
argc, _TCHAR
*
argv[])
{
CSignVerify::Instance().Initialize();
//
TestRule();
//
TestRuleImpl();
//
TestRuleImplMultiThread();
//
TestRuleTestImpl();
//
TestSign();
//
TestSignCacheAndProcNameCache();
//
TestUserConfig();
//
MichaelPeng: 测试代码未与工程代码清晰分离
TestLoadRule();
return
0
;
}
4 测试靠打印而不是assert,很难自动化验证。且测试代码未与工程代码分离。
见上例
5 太多的if-else而不会用表驱动
ppro\PrivacyProtection\rule\KSystemEnvirVar.h
class
KSystemEnvirVar
{
public
:
//
MichaelPeng: 应为静态函数
CString GetValue(LPCTSTR szVariable)
{
TCHAR szFolderPath[MAX_PATH
+
1
]
=
{
0
};
//
MichaelPeng: if else太多,应做成表驱动
if
(
0
==
_tcsicmp(szVariable, _T(
"
%Desktop%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_DESKTOP,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%Internet%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_INTERNET,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%Programs%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_PROGRAMS,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%Controls%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_CONTROLS,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%Printers%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_PRINTERS,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%Personal%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_PERSONAL,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%Favorites%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_FAVORITES,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%Startup%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_STARTUP,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%Recent%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_RECENT,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%Sendto%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_SENDTO,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%Bitbucket%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_BITBUCKET,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%StartMenu%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_STARTMENU,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%Mydocuments%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_MYDOCUMENTS,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%Mymusic%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_MYMUSIC,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%Myvideo%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_MYVIDEO,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%Desktopdirectory%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_DESKTOPDIRECTORY,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%Drives%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_DRIVES,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%Network%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_NETWORK,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%Nethood%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_NETHOOD,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%Fonts%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_FONTS,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%Templates%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_TEMPLATES,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%CommonStartMenu%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_COMMON_STARTMENU,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%CommonPrograms%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_COMMON_PROGRAMS,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%CommonStartup%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_COMMON_STARTUP,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%LocalAppdate%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_LOCAL_APPDATA,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%CommonAppdata%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_COMMON_APPDATA,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%Windows%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_WINDOWS,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%System32%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_SYSTEM,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%ProgramFilesComm%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_PROGRAM_FILES_COMMON,
0
);
}
else
if
(
0
==
_tcsicmp(szVariable, _T(
"
%CommonDocuments%
"
))) {
::SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_COMMON_DOCUMENTS,
0
);
}
else
{
CString strVariable(szVariable);
strVariable.Remove(_T(
'
%
'
));
TCHAR
*
szTemp
=
_tgetenv(strVariable);
if
(szTemp)
{
if
(_tcsstr(szTemp, L
"
~
"
))
{
::GetLongPathNameW(szTemp, szFolderPath, MAX_PATH);
}
else
{
_tcsncpy(szFolderPath, szTemp, MAX_PATH);
}
szFolderPath[MAX_PATH]
=
0
;
}
}
return
szFolderPath;
}
protected
:
};
6 函数逻辑不严格,有明显漏洞
ppro\PrivacyProtection\rule\KVariableString.h
BOOL KVariableString::RelpaceVariable(CString
&
strString)
{
BOOL bReturn
=
TRUE;
int
nStartPos;
int
nEndPos;
CString strTemp(strString);
CString strVariable;
CString strValue;
for
(;;)
{
nStartPos
=
strTemp.Find(_T(
'
%
'
),
0
);
if
(nStartPos
!=
-
1
)
nEndPos
=
strTemp.Find(_T(
'
%
'
), nStartPos
+
1
);
if
(nStartPos
==
-
1
||
nEndPos
==
-
1
)
break
;
strVariable
=
strTemp.Mid(nStartPos, nEndPos
-
nStartPos
+
1
);
strValue
=
GetVariable(strVariable);
if
(strValue.IsEmpty())
break
;
//
MichaelPeng: 对于%abc%xxx%def%abc%ghi%,会出现错误的替换,正确的是替换掉%abc%, %def%, %ghi%,但这段代码会替换掉两个%abc%
strTemp.Replace(strVariable, strValue);
}
if
(strTemp.Find(_T(
'
%
'
))
!=
-
1
)
{
#ifdef OUTPUT_INIT_WARNING
#ifdef __log_file__
CString strTempVar(strString);
strTempVar.Replace(_T(
"
%
"
), _T(
"
%%
"
));
DPrintW(L
"
KVariableString::RelpaceVariable fail, variablename:%s\n
"
, strTempVar);
CString strMsg;
strMsg.Format(_T(
"
查找环境变量失败:%s
"
), strString);
::MessageBox(NULL, strMsg, _T(
"
隐私保护器
"
), MB_OK);
#endif
#endif
return
FALSE;
}
//
MicaelPeng: 这个替换功能不能从函数名体现出来
do
{
nStartPos
=
strTemp.Find(_T(
"
\\\\
"
));
if
(nStartPos
==
-
1
)
break
;
strTemp.Replace(_T(
"
\\\\
"
), _T(
"
\\
"
));
}
while
(
true
);
strString
=
strTemp;
ppro\PrivacyProtection\rule\KFunction.cpp
//
MichaelPeng: DirCount是什么意思?路径数目?DirLength更合适
int
KFunction::GetDirCount(LPCTSTR szPath)
{
LPCTSTR pszChr
=
_tcsrchr(szPath, _T(
'
\\
'
));
if
(
!
pszChr)
return
-
1
;
return
pszChr
-
szPath
+
1
;
}
//
MicahelPeng: count应为length
int
KFunction::GetFileNameCount(LPCTSTR szPath)
{
LPCTSTR pszChr
=
_tcsrchr(szPath, _T(
'
\\
'
));
if
(
!
pszChr)
return
-
1
;
return
_tcslen(
++
pszChr);
}
//
MichaelPeng: count应为length
//
若文件没有后缀而路径有后缀,如C:\a.b.c\dfgh 则结果应为0或-1,但函数返回7
int
KFunction::GetFileExtCount(LPCTSTR szPath)
{
LPCTSTR pszChr
=
_tcsrchr(szPath, _T(
'
.
'
));
if
(
!
pszChr)
return
-
1
;
return
_tcslen(pszChr);
}
才看了一小部分代码就发现了这么多的问题。这还不是我看到的全部问题,只是挑了几个比较典型的。如果是园子里初学者练手的项目这种质量是没问题的。但号称专业级的安全保护模块就是这种级别的代码水准,不禁让人对其专业性产生疑虑。
作者: Michael Peng 发表于 2010-12-02 09:00 原文链接
评论: 128 查看评论 发表评论
最新新闻:
· 切客网试水酒店商务合作 探索LBS应用合作模式(2010-12-02 18:06)
· 谷歌街景服务因侵犯私人领地被判赔偿1美元(2010-12-02 18:05)
· 摩托罗拉收购4Home欲涉足智能家居(2010-12-02 18:02)
· 微软张亚勤:要有批判性思维不断挑战与争论(2010-12-02 17:57)
· Google宣布HTML5游戏大赛获胜者,《忍者跳》和《猴子要塞》胜出(2010-12-02 17:53)
编辑推荐:金山安全卫士代码批评
网站导航:博客园首页 我的园子 新闻 闪存 小组 博问 知识库