Skip to content

Add clang-tidy checker to look for imprecise sub-object bounds. #705

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/misc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
)

add_clang_library(clangTidyMiscModule
CheriRepresentableSubobjectCheck.cpp
DefinitionsInHeadersCheck.cpp
MiscTidyModule.cpp
MisleadingBidirectional.cpp
Expand Down
110 changes: 110 additions & 0 deletions clang-tools-extra/clang-tidy/misc/CheriRepresentableSubobjectCheck.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//===--- CheriRepresentableSubobjectCheck.cpp - clang-tidy ----------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "CheriRepresentableSubobjectCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/RecordLayout.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Basic/TargetInfo.h"
#include "llvm/CHERI/cheri-compressed-cap/cheri_compressed_cap.h"
#include "llvm/Support/Debug.h"

#include <limits>
#include <utility>

#define DEBUG_TYPE "cheri-representable-subobject-check"

using namespace clang::ast_matchers;

namespace {

template <typename CC>
std::pair<uint64_t, uint64_t>
getSubobjectRangeImpl(typename CC::addr_t ReqBase,
typename CC::addr_t ReqSize) {
typename CC::offset_t MaxTop =
static_cast<typename CC::offset_t>(
std::numeric_limits<typename CC::addr_t>::max()) +
1;
typename CC::cap_t TmpCap = CC::make_max_perms_cap(0, 0, MaxTop);
CC::setbounds(&TmpCap, ReqBase, ReqBase + ReqSize);

return {TmpCap.base(), TmpCap.top64()};
}

std::pair<uint64_t, uint64_t> getSubobjectRange(const clang::TargetInfo &TI,
uint64_t ReqBase,
uint64_t ReqSize) {
const uint64_t CapabilityWidth = TI.getCHERICapabilityWidth();

if (CapabilityWidth == 128) {
// XXX Need to support Morello as well
return getSubobjectRangeImpl<CompressedCap128>(ReqBase, ReqSize);
} else if (CapabilityWidth == 64) {
return getSubobjectRangeImpl<CompressedCap64>(ReqBase, ReqSize);
} else {
// Unsupported / unexpected
assert(false && "Should not be reached, unsupported capability width");
}
}

} // namespace

namespace clang {
namespace tidy {
namespace misc {

bool CheriRepresentableSubobjectCheck::isLanguageVersionSupported(
const LangOptions &LangOpts) const {
return (LangOpts.getCheriBounds() > LangOptions::CBM_Conservative);
}

void CheriRepresentableSubobjectCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(recordDecl(isDefinition()).bind("r"), this);
}

void CheriRepresentableSubobjectCheck::check(
const MatchFinder::MatchResult &Result) {
const TargetInfo &TI = Result.Context->getTargetInfo();
const auto *MatchedDecl = Result.Nodes.getNodeAs<RecordDecl>("r");
if (!MatchedDecl->getIdentifier())
return;
if (MatchedDecl->isInvalidDecl())
return;

const ASTRecordLayout &Layout =
Result.Context->getASTRecordLayout(MatchedDecl);
// For each field, determine whether it is representable
for (const FieldDecl *Field : MatchedDecl->fields()) {
// Not sure whether we care about distinguishing bitfields
uint64_t Offset = Layout.getFieldOffset(Field->getFieldIndex()) / 8;
TypeInfo Info = Result.Context->getTypeInfo(Field->getType());
uint64_t Size = Info.Width / 8;

auto BaseAndTop = getSubobjectRange(TI, Offset, Size);

if (BaseAndTop.first != Offset) {
diag(Field->getLocation(),
"Field %0 (%1) at %2 in %3 with size %4 may not be representable: "
"offset will be imprecisely aligned to %5")
<< Field << Field->getType() << Offset << MatchedDecl << Size
<< BaseAndTop.first;
}
if (Offset + Size != BaseAndTop.second) {
diag(Field->getLocation(),
"Field %0 (%1) at %2 in %3 with size %4 may not be representable: "
"top will be imprecisely aligned to %5")
<< Field << Field->getType() << Offset << MatchedDecl << Size
<< BaseAndTop.second;
}
}
}

} // namespace misc
} // namespace tidy
} // namespace clang
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//===--- CheriRepresentableSubobjectCheck.h - clang-tidy --------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_CHERIREPRESENTABLESUBOBJECTCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_CHERIREPRESENTABLESUBOBJECTCHECK_H

#include "../ClangTidyCheck.h"

namespace clang {
namespace tidy {
namespace misc {

/// CHERI-specific checker to detect possible unrepresentable sub-object bounds.
///
/// Note that this makes the assumption that the container structure is aligned
/// to a representable boundary.
class CheriRepresentableSubobjectCheck : public ClangTidyCheck {
public:
CheriRepresentableSubobjectCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {}
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override;
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
};

} // namespace misc
} // namespace tidy
} // namespace clang

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_CHERIREPRESENTABLESUBOBJECTCHECK_H
3 changes: 3 additions & 0 deletions clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "../ClangTidy.h"
#include "../ClangTidyModule.h"
#include "../ClangTidyModuleRegistry.h"
#include "CheriRepresentableSubobjectCheck.h"
#include "DefinitionsInHeadersCheck.h"
#include "MisleadingBidirectional.h"
#include "MisleadingIdentifier.h"
Expand All @@ -33,6 +34,8 @@ namespace misc {
class MiscModule : public ClangTidyModule {
public:
void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
CheckFactories.registerCheck<CheriRepresentableSubobjectCheck>(
"misc-cheri-representable-subobject");
CheckFactories.registerCheck<DefinitionsInHeadersCheck>(
"misc-definitions-in-headers");
CheckFactories.registerCheck<MisleadingBidirectionalCheck>(
Expand Down
6 changes: 6 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,12 @@ New checks
Finds virtual classes whose destructor is neither public and virtual nor
protected and non-virtual.

- New :doc:`misc-cheri-representable-subobject
<clang-tidy/checks/misc-cheri-representable-subobject>` check.

Finds structure fields that may result in imprecise sub-object bounds. This occurs because the
offset of the field or its size are not representable by a CHERI capability.

- New :doc:`misc-misleading-bidirectional <clang-tidy/checks/misc-misleading-bidirectional>` check.

Inspects string literal and comments for unterminated bidirectional Unicode
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/docs/clang-tidy/checks/list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ Clang-Tidy Checks
`llvmlibc-callee-namespace <llvmlibc-callee-namespace.html>`_,
`llvmlibc-implementation-in-namespace <llvmlibc-implementation-in-namespace.html>`_,
`llvmlibc-restrict-system-libc-headers <llvmlibc-restrict-system-libc-headers.html>`_, "Yes"
`misc-cheri-representable-subobject <misc-cheri-representable-subobject.html>`_, "Yes"
`misc-definitions-in-headers <misc-definitions-in-headers.html>`_, "Yes"
`misc-misleading-bidirectional <misc-misleading-bidirectional.html>`_,
`misc-misleading-identifier <misc-misleading-identifier.html>`_,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.. title:: clang-tidy - misc-cheri-representable-subobject

misc-cheri-representable-subobject
==================================

Find structures that contain problematic fields for CHERI sub-object bounds.
When the compiler is configured to narrow sub-object bounds, there is no
guarantee that the target structure field is representable.

In the following case:

.. code-block:: c

// Any size not representable by the target CHERI architecture
#define LARGE (1 << 15)

struct foo {
int32_t value; // offset = 0, size = 4
char buffer[LARGE]; // offset = 4, size = 32KiB
};

the ``buffer`` field will be prompted with a warning, because the CHERI
capability for ``&foo.buffer`` is not representable, under the assumption
that ``struct foo`` will be properly aligned for its size by the allocator.

There are two warnings. One is triggered when the field offset is
aligned down for representablity. Another warning is triggered when the
length of the field does not match the representable length of the capability.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// RUN: %check_clang_tidy -check-suffix=RV64 %s misc-cheri-representable-subobject %t -- -- -target riscv64-unknown-freebsd -march=rv64imafdcxcheri -mabi=l64pc128d -cheri-bounds=subobject-safe
// RUN: %check_clang_tidy -check-suffix=RV32 %s misc-cheri-representable-subobject %t -- -- -target riscv32-unknown-freebsd -march=rv32imafdcxcheri -mabi=il32pc64 -cheri-bounds=subobject-safe

struct test_large_subobject {
int skew_offset;
char large_buffer[((1 << 13) - 7)];
};
// CHECK-MESSAGES-RV64: :[[@LINE-2]]:8: warning: Field 'large_buffer' ('char[8185]') at 4 in 'test_large_subobject' with size 8185 may not be representable: offset will be imprecisely aligned to 0 [misc-cheri-representable-subobject]
// CHECK-MESSAGES-RV64: :[[@LINE-3]]:8: warning: Field 'large_buffer' ('char[8185]') at 4 in 'test_large_subobject' with size 8185 may not be representable: top will be imprecisely aligned to 8192 [misc-cheri-representable-subobject]
// CHECK-MESSAGES-RV32: :[[@LINE-4]]:8: warning: Field 'large_buffer' ('char[8185]') at 4 in 'test_large_subobject' with size 8185 may not be representable: offset will be imprecisely aligned to 0 [misc-cheri-representable-subobject]
// CHECK-MESSAGES-RV32: :[[@LINE-5]]:8: warning: Field 'large_buffer' ('char[8185]') at 4 in 'test_large_subobject' with size 8185 may not be representable: top will be imprecisely aligned to 8192 [misc-cheri-representable-subobject]

struct test_small_subobject {
int int_value;
long long_value;
char small_buffer[256];
void *pointer_value;
};
// CHECK-MESSAGES-RV32: :[[@LINE-3]]:8: warning: Field 'small_buffer' ('char[256]') at 8 in 'test_small_subobject' with size 256 may not be representable: offset will be imprecisely aligned to 0 [misc-cheri-representable-subobject]
// CHECK-MESSAGES-RV32: :[[@LINE-4]]:8: warning: Field 'small_buffer' ('char[256]') at 8 in 'test_small_subobject' with size 256 may not be representable: top will be imprecisely aligned to 288 [misc-cheri-representable-subobject]

struct test_mixed {
char pre1[33024];
char buf[2941200];
};
// CHECK-MESSAGES-RV64: :[[@LINE-2]]:8: warning: Field 'buf' ('char[2941200]') at 33024 in 'test_mixed' with size 2941200 may not be representable: offset will be imprecisely aligned to 32768 [misc-cheri-representable-subobject]
// CHECK-MESSAGES-RV64: :[[@LINE-3]]:8: warning: Field 'buf' ('char[2941200]') at 33024 in 'test_mixed' with size 2941200 may not be representable: top will be imprecisely aligned to 2977792 [misc-cheri-representable-subobject]
// CHECK-MESSAGES-RV32: :[[@LINE-5]]:8: warning: Field 'pre1' ('char[33024]') at 0 in 'test_mixed' with size 33024 may not be representable: top will be imprecisely aligned to 36864 [misc-cheri-representable-subobject]
// CHECK-MESSAGES-RV32: :[[@LINE-5]]:8: warning: Field 'buf' ('char[2941200]') at 33024 in 'test_mixed' with size 2941200 may not be representable: offset will be imprecisely aligned to 0 [misc-cheri-representable-subobject]
// CHECK-MESSAGES-RV32: :[[@LINE-6]]:8: warning: Field 'buf' ('char[2941200]') at 33024 in 'test_mixed' with size 2941200 may not be representable: top will be imprecisely aligned to 3145728 [misc-cheri-representable-subobject]

struct test_flexible {
int int_value;
char flexbuf[];
};