Skip to content

Commit 5ddd656

Browse files
committed
[analyzer] Add a syntactic security check for ObjC NSCoder API.
Method '-[NSCoder decodeValueOfObjCType:at:]' is not only deprecated but also a security hazard, hence a loud check. Differential Revision: https://reviews.llvm.org/D71728 (cherry picked from commit b284005)
1 parent 89dda46 commit 5ddd656

File tree

5 files changed

+129
-1
lines changed

5 files changed

+129
-1
lines changed

clang/include/clang/StaticAnalyzer/Checkers/Checkers.td

+5
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,11 @@ def DeprecatedOrUnsafeBufferHandling :
773773
Dependencies<[SecuritySyntaxChecker]>,
774774
Documentation<HasDocumentation>;
775775

776+
def decodeValueOfObjCType : Checker<"decodeValueOfObjCType">,
777+
HelpText<"Warn on uses of the '-decodeValueOfObjCType:at:' method">,
778+
Dependencies<[SecuritySyntaxChecker]>,
779+
Documentation<HasDocumentation>;
780+
776781
} // end "security.insecureAPI"
777782

778783
let ParentPackage = Security in {

clang/lib/Driver/ToolChains/Clang.cpp

+4-1
Original file line numberDiff line numberDiff line change
@@ -2611,8 +2611,11 @@ static void RenderAnalyzerOptions(const ArgList &Args, ArgStringList &CmdArgs,
26112611
CmdArgs.push_back("-analyzer-disable-checker=unix.Vfork");
26122612
}
26132613

2614-
if (Triple.isOSDarwin())
2614+
if (Triple.isOSDarwin()) {
26152615
CmdArgs.push_back("-analyzer-checker=osx");
2616+
CmdArgs.push_back(
2617+
"-analyzer-checker=security.insecureAPI.decodeValueOfObjCType");
2618+
}
26162619

26172620
CmdArgs.push_back("-analyzer-checker=deadcode");
26182621

clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp

+68
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ struct ChecksFilter {
4949
DefaultBool check_vfork;
5050
DefaultBool check_FloatLoopCounter;
5151
DefaultBool check_UncheckedReturn;
52+
DefaultBool check_decodeValueOfObjCType;
5253

5354
CheckerNameRef checkName_bcmp;
5455
CheckerNameRef checkName_bcopy;
@@ -63,6 +64,7 @@ struct ChecksFilter {
6364
CheckerNameRef checkName_vfork;
6465
CheckerNameRef checkName_FloatLoopCounter;
6566
CheckerNameRef checkName_UncheckedReturn;
67+
CheckerNameRef checkName_decodeValueOfObjCType;
6668
};
6769

6870
class WalkAST : public StmtVisitor<WalkAST> {
@@ -83,6 +85,7 @@ class WalkAST : public StmtVisitor<WalkAST> {
8385

8486
// Statement visitor methods.
8587
void VisitCallExpr(CallExpr *CE);
88+
void VisitObjCMessageExpr(ObjCMessageExpr *CE);
8689
void VisitForStmt(ForStmt *S);
8790
void VisitCompoundStmt (CompoundStmt *S);
8891
void VisitStmt(Stmt *S) { VisitChildren(S); }
@@ -93,6 +96,7 @@ class WalkAST : public StmtVisitor<WalkAST> {
9396
bool checkCall_strCommon(const CallExpr *CE, const FunctionDecl *FD);
9497

9598
typedef void (WalkAST::*FnCheck)(const CallExpr *, const FunctionDecl *);
99+
typedef void (WalkAST::*MsgCheck)(const ObjCMessageExpr *);
96100

97101
// Checker-specific methods.
98102
void checkLoopConditionForFloat(const ForStmt *FS);
@@ -110,6 +114,7 @@ class WalkAST : public StmtVisitor<WalkAST> {
110114
void checkCall_rand(const CallExpr *CE, const FunctionDecl *FD);
111115
void checkCall_random(const CallExpr *CE, const FunctionDecl *FD);
112116
void checkCall_vfork(const CallExpr *CE, const FunctionDecl *FD);
117+
void checkMsg_decodeValueOfObjCType(const ObjCMessageExpr *ME);
113118
void checkUncheckedReturnValue(CallExpr *CE);
114119
};
115120
} // end anonymous namespace
@@ -182,6 +187,20 @@ void WalkAST::VisitCallExpr(CallExpr *CE) {
182187
VisitChildren(CE);
183188
}
184189

190+
void WalkAST::VisitObjCMessageExpr(ObjCMessageExpr *ME) {
191+
MsgCheck evalFunction =
192+
llvm::StringSwitch<MsgCheck>(ME->getSelector().getAsString())
193+
.Case("decodeValueOfObjCType:at:",
194+
&WalkAST::checkMsg_decodeValueOfObjCType)
195+
.Default(nullptr);
196+
197+
if (evalFunction)
198+
(this->*evalFunction)(ME);
199+
200+
// Recurse and check children.
201+
VisitChildren(ME);
202+
}
203+
185204
void WalkAST::VisitCompoundStmt(CompoundStmt *S) {
186205
for (Stmt *Child : S->children())
187206
if (Child) {
@@ -923,6 +942,54 @@ void WalkAST::checkCall_vfork(const CallExpr *CE, const FunctionDecl *FD) {
923942
CELoc, CE->getCallee()->getSourceRange());
924943
}
925944

945+
//===----------------------------------------------------------------------===//
946+
// Check: '-decodeValueOfObjCType:at:' should not be used.
947+
// It is deprecated in favor of '-decodeValueOfObjCType:at:size:' due to
948+
// likelihood of buffer overflows.
949+
//===----------------------------------------------------------------------===//
950+
951+
void WalkAST::checkMsg_decodeValueOfObjCType(const ObjCMessageExpr *ME) {
952+
if (!filter.check_decodeValueOfObjCType)
953+
return;
954+
955+
// Check availability of the secure alternative:
956+
// iOS 11+, macOS 10.13+, tvOS 11+, and watchOS 4.0+
957+
// FIXME: We probably shouldn't register the check if it's not available.
958+
const TargetInfo &TI = AC->getASTContext().getTargetInfo();
959+
const llvm::Triple &T = TI.getTriple();
960+
const VersionTuple &VT = TI.getPlatformMinVersion();
961+
switch (T.getOS()) {
962+
case llvm::Triple::IOS:
963+
if (VT < VersionTuple(11, 0))
964+
return;
965+
break;
966+
case llvm::Triple::MacOSX:
967+
if (VT < VersionTuple(10, 13))
968+
return;
969+
break;
970+
case llvm::Triple::WatchOS:
971+
if (VT < VersionTuple(4, 0))
972+
return;
973+
break;
974+
case llvm::Triple::TvOS:
975+
if (VT < VersionTuple(11, 0))
976+
return;
977+
break;
978+
default:
979+
return;
980+
}
981+
982+
PathDiagnosticLocation MELoc =
983+
PathDiagnosticLocation::createBegin(ME, BR.getSourceManager(), AC);
984+
BR.EmitBasicReport(
985+
AC->getDecl(), filter.checkName_decodeValueOfObjCType,
986+
"Potential buffer overflow in '-decodeValueOfObjCType:at:'", "Security",
987+
"Deprecated method '-decodeValueOfObjCType:at:' is insecure "
988+
"as it can lead to potential buffer overflows. Use the safer "
989+
"'-decodeValueOfObjCType:at:size:' method.",
990+
MELoc, ME->getSourceRange());
991+
}
992+
926993
//===----------------------------------------------------------------------===//
927994
// Check: Should check whether privileges are dropped successfully.
928995
// Originally: <rdar://problem/6337132>
@@ -1035,3 +1102,4 @@ REGISTER_CHECKER(vfork)
10351102
REGISTER_CHECKER(FloatLoopCounter)
10361103
REGISTER_CHECKER(UncheckedReturn)
10371104
REGISTER_CHECKER(DeprecatedOrUnsafeBufferHandling)
1105+
REGISTER_CHECKER(decodeValueOfObjCType)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// RUN: %clang_analyze_cc1 -triple thumbv7-apple-ios11.0 -verify=available \
2+
// RUN: -analyzer-checker=security.insecureAPI.decodeValueOfObjCType %s
3+
// RUN: %clang_analyze_cc1 -triple thumbv7-apple-ios10.0 -verify=notavailable \
4+
// RUN: -analyzer-checker=security.insecureAPI.decodeValueOfObjCType %s
5+
// RUN: %clang_analyze_cc1 -triple x86_64-apple-macos10.13 -verify=available \
6+
// RUN: -analyzer-checker=security.insecureAPI.decodeValueOfObjCType %s
7+
// RUN: %clang_analyze_cc1 -triple x86_64-apple-macos10.12 -verify=notavailable \
8+
// RUN: -analyzer-checker=security.insecureAPI.decodeValueOfObjCType %s
9+
// RUN: %clang_analyze_cc1 -triple thumbv7-apple-watchos4.0 -verify=available \
10+
// RUN: -analyzer-checker=security.insecureAPI.decodeValueOfObjCType %s
11+
// RUN: %clang_analyze_cc1 -triple thumbv7-apple-watchos3.0 -verify=notavailable \
12+
// RUN: -analyzer-checker=security.insecureAPI.decodeValueOfObjCType %s
13+
// RUN: %clang_analyze_cc1 -triple thumbv7-apple-tvos11.0 -verify=available \
14+
// RUN: -analyzer-checker=security.insecureAPI.decodeValueOfObjCType %s
15+
// RUN: %clang_analyze_cc1 -triple thumbv7-apple-tvos10.0 -verify=notavailable \
16+
// RUN: -analyzer-checker=security.insecureAPI.decodeValueOfObjCType %s
17+
18+
// notavailable-no-diagnostics
19+
20+
typedef unsigned long NSUInteger;
21+
22+
@interface NSCoder
23+
- (void)decodeValueOfObjCType:(const char *)type
24+
at:(void *)data;
25+
- (void)decodeValueOfObjCType:(const char *)type
26+
at:(void *)data
27+
size:(NSUInteger)size;
28+
@end
29+
30+
void test(NSCoder *decoder) {
31+
// This would be a vulnerability on 64-bit platforms
32+
// but not on 32-bit platforms.
33+
NSUInteger x;
34+
[decoder decodeValueOfObjCType:"I" at:&x]; // available-warning{{Deprecated method '-decodeValueOfObjCType:at:' is insecure as it can lead to potential buffer overflows. Use the safer '-decodeValueOfObjCType:at:size:' method}}
35+
[decoder decodeValueOfObjCType:"I" at:&x size:sizeof(x)]; // no-warning
36+
}

clang/www/analyzer/available_checks.html

+16
Original file line numberDiff line numberDiff line change
@@ -1446,6 +1446,22 @@ <h3 id="security_checkers">Security Checkers</h3>
14461446
}
14471447
</pre></div></div></td></tr>
14481448

1449+
1450+
<tr><td><a id="security.insecureAPI.decodeValueOfObjCType"><div class="namedescr expandable"><span class="name">
1451+
security.insecureAPI.decodeValueOfObjCType</span><span class="lang">
1452+
(ObjC)</span><div class="descr">
1453+
Warn on uses of the <code>-[NSCoder decodeValueOfObjCType:at:]</code> method.
1454+
The safe alternative is <code>-[NSCoder decodeValueOfObjCType:at:size:]</code>.</div></div></a></td>
1455+
<td><div class="exampleContainer expandable">
1456+
<div class="example"><pre>
1457+
void test(NSCoder *decoder) {
1458+
// This would be a vulnerability on 64-bit platforms
1459+
// but not on 32-bit platforms.
1460+
NSUInteger x;
1461+
[decoder decodeValueOfObjCType:"I" at:&x]; // warn
1462+
}
1463+
</pre></div></div></td></tr>
1464+
14491465
</tbody></table>
14501466

14511467
<!-- =========================== unix =========================== -->

0 commit comments

Comments
 (0)