-
Notifications
You must be signed in to change notification settings - Fork 341
[-Wunsafe-buffer-usage][BoundsSafety] Extend safe patterns of arguments to counted_by parameters #9971
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
[-Wunsafe-buffer-usage][BoundsSafety] Extend safe patterns of arguments to counted_by parameters #9971
Conversation
…ts to counted_by parameters Add support of - overloaded subscript operator of hardened view/containers, and - calls to const member functions of 0 arguments Now it can match patterns like: ``` std::span<char> D, S; std::array<std::span<char>, 10> A; memcpy(D.first(S.size()).data(), S.data(), S.size()); memcpy(D.first(A[x].size()).data(), A[x].data(), A[x].size()); ``` (rdar://143986251)
Visit(SelfOpCall->getArg(1), OtherOpCall->getArg(1)); | ||
} | ||
return false; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we check for overloaded operator[]
and not just any operator[]
? Why do we need to check for hardened containers/view instead of any container?
Is this related to how the analysis work and gadgets are matched? Say, if we allow operator[]
on a pointer here and consider the call safe, then other gadget for pointer arithmetic won't match?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you are right. It should be just any operator[]
.
if (MD->isConst() && MD->param_empty()) { | ||
if (auto *OtherCall = | ||
dyn_cast<CXXMemberCallExpr>(Other->IgnoreParenImpCasts())) { | ||
if (OtherCall->getMethodDecl() == MD) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the pointer MD
and OtherCall->getMethodDecl()
different if they are in different template instances?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They should be different. I will add a test for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm... having an integrated test for the case where only methods are different is actually difficult. Because if methods are from different template instances, the implicit objects cannot be identical. Eventually, the test does not tell which condition fails the compatibility checking.
const T *data() const noexcept; | ||
T *data() noexcept; | ||
size_t size() const noexcept; | ||
T& operator[](size_t idx) const {return *data_ptr;} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I think just a declaration without the implementation would be fine and consistent with above declarations. Then, you could remove
T * data_ptr
above. const
operator[]
should returnconst T &
. You probably can dropconst
fromoperator[]
to make the tests work.- Nit: clang-format
T& operator[]
->T &operator[]
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems to require a definition when the template parameter T
has no linkage: https://godbolt.org/z/4MP4afqq5. I didn't know this before. I will fix it.
@@ -40,6 +42,7 @@ struct span { | |||
span<T> first(size_t count) const noexcept; | |||
span<T> last(size_t count) const noexcept; | |||
span<T> subspan(size_t offset, size_t count) const noexcept; | |||
T& operator[](size_t idx) const {return *data();}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
cb_cint(S.first(ttp->tp->a[x]).data(), ttp->tp->a[x]); | ||
cb_cint(S.first(t.a[x]).data(), tp->a[x]); // expected-warning{{unsafe assignment to function parameter of count-attributed type}} | ||
cb_cint(S.first(tt.t.a[x]).data(), tt.tp->a[x]); // expected-warning{{unsafe assignment to function parameter of count-attributed type}} | ||
cb_cint(S.first(ttp->t.a[x]).data(), ttp->tp->a[x]); // expected-warning{{unsafe assignment to function parameter of count-attributed type}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a few tests where subscript is in a member base? For example:
std::array<TT, 10> arr;
cb_cint(S.first(arr[1].t.a[x]).data(), arr[1].t.a[x]);
cb_cint(S.first(arr[1].t.a[x]).data(), arr[2].t.a[x]); // warn
if (const auto *OtherME = | ||
dyn_cast<MemberExpr>(Other->IgnoreParenImpCasts())) { | ||
return Self->getMemberDecl() == OtherME->getMemberDecl() && | ||
Visit(Self->getBase(), OtherME->getBase()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this work if the member base is CXXThisExpr
? For example:
struct Foo {
std::array<int, 10> A;
void foo(std::span<int> S) {
cb_cint(S.first(A[5]).data(), A[5]);
}
};
We probably can implement VisitCXXThisExpr
to make it work. Moreover, if this works, we might be able to remove isSameMemberBase
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I should have added tests of this case. I think it works. But why we can remove isSameMemberBase
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are using here Visit(Self->getBase(), OtherME->getBase())
to check if the base is the same. Couldn't we replace isSameMemberBase(Self->getBase(), MemberBase)
above by Visit(Self->getBase(), MemberBase)
to achieve the same?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see what you mean. I agree.
|
||
if (auto *MD = dyn_cast<CXXMethodDecl>(SelfOpCall->getCalleeDecl())) | ||
if (!MD->isConst()) | ||
return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand correctly, we check MD->isConst()
to ensure that both calls return the same value, and thus we can assume that the pattern is always safe.
Can dyn_cast<CXXMethodDecl>(SelfOpCall->getCalleeDecl())
be nullptr
? If so, we skip the MD->isConst()
check and we might consider as safe something we don't want. Maybe we can change this to:
const auto *MD = dyn_cast<CXXMethodDecl>(SelfOpCall->getCalleeDecl());
if (!MD || !MD->isConst())
return false;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the catch. It is my bad code! Yeah, I think it should be in the form you suggested.
I just learned that operator[]
can be static: https://en.cppreference.com/w/cpp/language/operators.
So we do need the dyn_cast
.
#10156) * [-Wunsafe-buffer-usage][BoundsSafety] Extend safe patterns of arguments to counted_by parameters (#9971) Add support of - overloaded subscript operator of hardened view/containers, and - calls to const member functions of 0 arguments Now it can match patterns like: ``` std::span<char> D, S; std::array<std::span<char>, 10> A; memcpy(D.first(S.size()).data(), S.data(), S.size()); memcpy(D.first(A[x].size()).data(), A[x].data(), A[x].size()); ``` (rdar://143986251) * [Safe Buffers][BoundsSafety] Fix a bug in the interop analysis that can cause infinite loops The interop analysis substitutes formal parameters to arguments in AST visiting. Conceptually, the substitution should be done exactly once. But the previous implementation never checked if the substitution has happened. In cases where the argument contains DREs referring to the Decl of the corresponding formal parameter, the analysis enters an infinite loop. A typical example is the following. ``` void f(int * __counted_by(n) p, size_t n) { f(p, n); } ``` This commit fixes the issue. (rdar://145705060) (cherry picked from commit 31f72f7)
Add support of
Now it can match patterns like:
(rdar://143986251)