Skip to content

Commit

Permalink
[clang-tidy] Add a new Android check "android-cloexec-socket"
Browse files Browse the repository at this point in the history
Summary: socket() is better to include SOCK_CLOEXEC in its type argument to avoid the file descriptor leakage.

Reviewers: chh, Eugene.Zelenko, alexfh, hokein, aaron.ballman

Reviewed By: chh, alexfh

Subscribers: srhines, mgorny, JDevlieghere, xazax.hun, cfe-commits

Tags: #clang-tools-extra

Differential Revision: https://reviews.llvm.org/D34913

llvm-svn: 307818
  • Loading branch information
yan-wang-google committed Jul 12, 2017
1 parent 4fc6966 commit b38045d
Show file tree
Hide file tree
Showing 11 changed files with 233 additions and 29 deletions.
2 changes: 2 additions & 0 deletions clang-tools-extra/clang-tidy/android/AndroidTidyModule.cpp
Expand Up @@ -13,6 +13,7 @@
#include "CloexecCreatCheck.h"
#include "CloexecFopenCheck.h"
#include "CloexecOpenCheck.h"
#include "CloexecSocketCheck.h"

using namespace clang::ast_matchers;

Expand All @@ -27,6 +28,7 @@ class AndroidModule : public ClangTidyModule {
CheckFactories.registerCheck<CloexecCreatCheck>("android-cloexec-creat");
CheckFactories.registerCheck<CloexecFopenCheck>("android-cloexec-fopen");
CheckFactories.registerCheck<CloexecOpenCheck>("android-cloexec-open");
CheckFactories.registerCheck<CloexecSocketCheck>("android-cloexec-socket");
}
};

Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/android/CMakeLists.txt
Expand Up @@ -5,6 +5,7 @@ add_clang_library(clangTidyAndroidModule
CloexecCreatCheck.cpp
CloexecFopenCheck.cpp
CloexecOpenCheck.cpp
CloexecSocketCheck.cpp

LINK_LIBS
clangAST
Expand Down
32 changes: 3 additions & 29 deletions clang-tools-extra/clang-tidy/android/CloexecOpenCheck.cpp
Expand Up @@ -8,6 +8,7 @@
//===----------------------------------------------------------------------===//

#include "CloexecOpenCheck.h"
#include "../utils/ASTUtils.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Lex/Lexer.h"
Expand All @@ -18,35 +19,8 @@ namespace clang {
namespace tidy {
namespace android {

namespace {
static constexpr const char *O_CLOEXEC = "O_CLOEXEC";

bool hasCloseOnExecFlag(const Expr *Flags, const SourceManager &SM,
const LangOptions &LangOpts) {
// If the Flag is an integer constant, check it.
if (isa<IntegerLiteral>(Flags)) {
if (!SM.isMacroBodyExpansion(Flags->getLocStart()) &&
!SM.isMacroArgExpansion(Flags->getLocStart()))
return false;

// Get the Marco name.
auto MacroName = Lexer::getSourceText(
CharSourceRange::getTokenRange(Flags->getSourceRange()), SM, LangOpts);

return MacroName == O_CLOEXEC;
}
// If it's a binary OR operation.
if (const auto *BO = dyn_cast<BinaryOperator>(Flags))
if (BO->getOpcode() == clang::BinaryOperatorKind::BO_Or)
return hasCloseOnExecFlag(BO->getLHS()->IgnoreParenCasts(), SM,
LangOpts) ||
hasCloseOnExecFlag(BO->getRHS()->IgnoreParenCasts(), SM, LangOpts);

// Otherwise, assume it has the flag.
return true;
}
} // namespace

void CloexecOpenCheck::registerMatchers(MatchFinder *Finder) {
auto CharPointerType = hasType(pointerType(pointee(isAnyCharacter())));

Expand Down Expand Up @@ -82,8 +56,8 @@ void CloexecOpenCheck::check(const MatchFinder::MatchResult &Result) {

// Check the required flag.
SourceManager &SM = *Result.SourceManager;
if (hasCloseOnExecFlag(FlagArg->IgnoreParenCasts(), SM,
Result.Context->getLangOpts()))
if (utils::exprHasBitFlagWithSpelling(FlagArg->IgnoreParenCasts(), SM,
Result.Context->getLangOpts(), O_CLOEXEC))
return;

SourceLocation EndLoc =
Expand Down
57 changes: 57 additions & 0 deletions clang-tools-extra/clang-tidy/android/CloexecSocketCheck.cpp
@@ -0,0 +1,57 @@
//===--- CloexecSocketCheck.cpp - clang-tidy-------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "CloexecSocketCheck.h"
#include "../utils/ASTUtils.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"

using namespace clang::ast_matchers;

namespace clang {
namespace tidy {
namespace android {

static constexpr const char *SOCK_CLOEXEC = "SOCK_CLOEXEC";

void CloexecSocketCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(
callExpr(callee(functionDecl(isExternC(), returns(isInteger()),
hasName("socket"),
hasParameter(0, hasType(isInteger())),
hasParameter(1, hasType(isInteger())),
hasParameter(2, hasType(isInteger())))
.bind("funcDecl")))
.bind("socketFn"),
this);
}

void CloexecSocketCheck::check(const MatchFinder::MatchResult &Result) {
const auto *MatchedCall = Result.Nodes.getNodeAs<CallExpr>("socketFn");
const auto *FD = Result.Nodes.getNodeAs<FunctionDecl>("funcDecl");
const Expr *FlagArg = MatchedCall->getArg(1);
SourceManager &SM = *Result.SourceManager;

if (utils::exprHasBitFlagWithSpelling(FlagArg->IgnoreParenCasts(), SM,
Result.Context->getLangOpts(), SOCK_CLOEXEC))
return;

SourceLocation EndLoc =
Lexer::getLocForEndOfToken(SM.getFileLoc(FlagArg->getLocEnd()), 0, SM,
Result.Context->getLangOpts());

diag(EndLoc, "%0 should use %1 where possible")
<< FD << SOCK_CLOEXEC
<< FixItHint::CreateInsertion(EndLoc,
(Twine(" | ") + SOCK_CLOEXEC).str());
}

} // namespace android
} // namespace tidy
} // namespace clang
35 changes: 35 additions & 0 deletions clang-tools-extra/clang-tidy/android/CloexecSocketCheck.h
@@ -0,0 +1,35 @@
//===--- CloexecSocketCheck.h - clang-tidy-----------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_CLOEXEC_SOCKET_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_CLOEXEC_SOCKET_H

#include "../ClangTidy.h"

namespace clang {
namespace tidy {
namespace android {

/// Finds code that uses socket() without using the SOCK_CLOEXEC flag.
///
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/android-cloexec-socket.html
class CloexecSocketCheck : public ClangTidyCheck {
public:
CloexecSocketCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {}
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
};

} // namespace android
} // namespace tidy
} // namespace clang

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_CLOEXEC_SOCKET_H
28 changes: 28 additions & 0 deletions clang-tools-extra/clang-tidy/utils/ASTUtils.cpp
Expand Up @@ -11,6 +11,7 @@

#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Lex/Lexer.h"

namespace clang {
namespace tidy {
Expand Down Expand Up @@ -39,6 +40,33 @@ bool IsBinaryOrTernary(const Expr *E) {
return false;
}

bool exprHasBitFlagWithSpelling(const Expr *Flags, const SourceManager &SM,
const LangOptions &LangOpts,
StringRef FlagName) {
// If the Flag is an integer constant, check it.
if (isa<IntegerLiteral>(Flags)) {
if (!SM.isMacroBodyExpansion(Flags->getLocStart()) &&
!SM.isMacroArgExpansion(Flags->getLocStart()))
return false;

// Get the marco name.
auto MacroName = Lexer::getSourceText(
CharSourceRange::getTokenRange(Flags->getSourceRange()), SM, LangOpts);

return MacroName == FlagName;
}
// If it's a binary OR operation.
if (const auto *BO = dyn_cast<BinaryOperator>(Flags))
if (BO->getOpcode() == clang::BinaryOperatorKind::BO_Or)
return exprHasBitFlagWithSpelling(BO->getLHS()->IgnoreParenCasts(), SM,
LangOpts, FlagName) ||
exprHasBitFlagWithSpelling(BO->getRHS()->IgnoreParenCasts(), SM,
LangOpts, FlagName);

// Otherwise, assume it has the flag.
return true;
}

} // namespace utils
} // namespace tidy
} // namespace clang
7 changes: 7 additions & 0 deletions clang-tools-extra/clang-tidy/utils/ASTUtils.h
Expand Up @@ -20,6 +20,13 @@ const FunctionDecl *getSurroundingFunction(ASTContext &Context,
const Stmt &Statement);
// Determine whether Expr is a Binary or Ternary expression.
bool IsBinaryOrTernary(const Expr *E);

/// Checks whether a macro flag is present in the given argument. Only considers
/// cases of single match or match in a binary OR expression. For example,
/// <needed-flag> or <flag> | <needed-flag> | ...
bool exprHasBitFlagWithSpelling(const Expr *Flags, const SourceManager &SM,
const LangOptions &LangOpts,
StringRef FlagName);
} // namespace utils
} // namespace tidy
} // namespace clang
Expand Down
6 changes: 6 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Expand Up @@ -73,6 +73,12 @@ Improvements to clang-tidy

Checks if the required mode ``e`` exists in the mode argument of ``fopen()``.

- New `android-cloexec-socket
<http://clang.llvm.org/extra/clang-tidy/checks/android-cloexec-socket.html>`_ check

Checks if the required file flag ``SOCK_CLOEXEC`` is present in the argument of
``socket()``.

- New `cert-dcl21-cpp
<http://clang.llvm.org/extra/clang-tidy/checks/cert-dcl21-cpp.html>`_ check

Expand Down
@@ -0,0 +1,18 @@
.. title:: clang-tidy - android-cloexec-socket

android-cloexec-socket
======================

``socket()`` should include ``SOCK_CLOEXEC`` in its type argument to avoid the
file descriptor leakage. Without this flag, an opened sensitive file would
remain open across a fork+exec to a lower-privileged SELinux domain.

Examples:

.. code-block:: c++

socket(domain, type, SOCK_STREAM);

// becomes

socket(domain, type, SOCK_STREAM | SOCK_CLOEXEC);
1 change: 1 addition & 0 deletions clang-tools-extra/docs/clang-tidy/checks/list.rst
Expand Up @@ -7,6 +7,7 @@ Clang-Tidy Checks
android-cloexec-creat
android-cloexec-fopen
android-cloexec-open
android-cloexec-socket
boost-use-to-string
cert-dcl03-c (redirects to misc-static-assert) <cert-dcl03-c>
cert-dcl21-cpp
Expand Down
75 changes: 75 additions & 0 deletions clang-tools-extra/test/clang-tidy/android-cloexec-socket.cpp
@@ -0,0 +1,75 @@
// RUN: %check_clang_tidy %s android-cloexec-socket %t

#define SOCK_STREAM 1
#define SOCK_DGRAM 2
#define __O_CLOEXEC 3
#define SOCK_CLOEXEC __O_CLOEXEC
#define TEMP_FAILURE_RETRY(exp) \
({ \
int _rc; \
do { \
_rc = (exp); \
} while (_rc == -1); \
})

extern "C" int socket(int domain, int type, int protocol);

void a() {
socket(0, SOCK_STREAM, 0);
// CHECK-MESSAGES: :[[@LINE-1]]:24: warning: 'socket' should use SOCK_CLOEXEC where possible [android-cloexec-socket]
// CHECK-FIXES: socket(0, SOCK_STREAM | SOCK_CLOEXEC, 0)
TEMP_FAILURE_RETRY(socket(0, SOCK_STREAM, 0));
// CHECK-MESSAGES: :[[@LINE-1]]:43: warning: 'socket'
// CHECK-FIXES: TEMP_FAILURE_RETRY(socket(0, SOCK_STREAM | SOCK_CLOEXEC, 0))
socket(0, SOCK_STREAM | SOCK_DGRAM, 0);
// CHECK-MESSAGES: :[[@LINE-1]]:37: warning: 'socket'
// CHECK-FIXES: socket(0, SOCK_STREAM | SOCK_DGRAM | SOCK_CLOEXEC, 0)
TEMP_FAILURE_RETRY(socket(0, SOCK_STREAM | SOCK_DGRAM, 0));
// CHECK-MESSAGES: :[[@LINE-1]]:56: warning: 'socket'
// CHECK-FIXES: TEMP_FAILURE_RETRY(socket(0, SOCK_STREAM | SOCK_DGRAM | SOCK_CLOEXEC, 0))
}

void f() {
socket(0, 3, 0);
// CHECK-MESSAGES: :[[@LINE-1]]:14: warning: 'socket'
// CHECK-FIXES: socket(0, 3 | SOCK_CLOEXEC, 0)
TEMP_FAILURE_RETRY(socket(0, 3, 0));
// CHECK-MESSAGES: :[[@LINE-1]]:33: warning: 'socket'
// CHECK-FIXES: TEMP_FAILURE_RETRY(socket(0, 3 | SOCK_CLOEXEC, 0))

int flag = 3;
socket(0, flag, 0);
TEMP_FAILURE_RETRY(socket(0, flag, 0));
}

namespace i {
int socket(int domain, int type, int protocol);

void d() {
socket(0, SOCK_STREAM, 0);
TEMP_FAILURE_RETRY(socket(0, SOCK_STREAM, 0));
socket(0, SOCK_STREAM | SOCK_DGRAM, 0);
TEMP_FAILURE_RETRY(socket(0, SOCK_STREAM | SOCK_DGRAM, 0));
}

} // namespace i

void e() {
socket(0, SOCK_CLOEXEC, 0);
TEMP_FAILURE_RETRY(socket(0, SOCK_CLOEXEC, 0));
socket(0, SOCK_STREAM | SOCK_CLOEXEC, 0);
TEMP_FAILURE_RETRY(socket(0, SOCK_STREAM | SOCK_CLOEXEC, 0));
socket(0, SOCK_STREAM | SOCK_CLOEXEC | SOCK_DGRAM, 0);
TEMP_FAILURE_RETRY(socket(0, SOCK_STREAM | SOCK_CLOEXEC | SOCK_DGRAM, 0));
}

class G {
public:
int socket(int domain, int type, int protocol);
void d() {
socket(0, SOCK_STREAM, 0);
TEMP_FAILURE_RETRY(socket(0, SOCK_STREAM, 0));
socket(0, SOCK_STREAM | SOCK_DGRAM, 0);
TEMP_FAILURE_RETRY(socket(0, SOCK_STREAM | SOCK_DGRAM, 0));
}
};

0 comments on commit b38045d

Please sign in to comment.