Skip to content

Swift: Query for escaping parameters of unsafe closures - #13706

Draft
rdmarsh2 wants to merge 9 commits into
mainfrom
rdmarsh2/swift/withUnsafeBytes
Draft

Swift: Query for escaping parameters of unsafe closures#13706
rdmarsh2 wants to merge 9 commits into
mainfrom
rdmarsh2/swift/withUnsafeBytes

Conversation

@rdmarsh2

Copy link
Copy Markdown
Contributor

No description provided.

@github-actions github-actions Bot added the Swift label Jul 10, 2023
Comment thread swift/ql/test/query-tests/Security/CWE-825/test.ql Fixed
Comment thread swift/ql/test/query-tests/Security/CWE-825/test.ql Fixed

@geoffw0 geoffw0 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good early results, this looks promising.

Comment thread swift/ql/test/query-tests/Security/CWE-825/withUnsafeBytes.swift
ints.withUnsafeBytes({(p: UnsafeRawBufferPointer) in v.field = p}) // BAD
print(v.field)

ints.withUnsafeBytes(myPrint) // GOOD

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another variant here would be calling a member function that stores the pointer argument in the object it's called on for later use. The effect is similar to the v.field = p case.

Comment thread swift/ql/test/query-tests/Security/CWE-825/test.ql Outdated

predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) {
isSink(node, _) and
c = any(DataFlow::ContentSet set)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a cartesian product - might be a performance issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is (and I hit the performance issue on a real database). I've shrunk it by narrowing the sinks.


from Flow::PathNode source, Flow::PathNode sink
where Flow::flowPath(source, sink)
select sink, source, sink, "This unsafe parameter may escape its invocation"

Check warning

Code scanning / CodeQL

Alert message style violation

Alert message should end with a full stop.
Comment thread swift/ql/src/queries/Security/CWE-825/UnsafePointerEscapesClosure.ql Outdated
@github-actions

Copy link
Copy Markdown
Contributor

QHelp previews:

swift/ql/src/queries/Security/CWE-825/UnsafePointerEscapesClosure.qhelp

Unsafe pointer escapes closure

Certain functions take a closure and pass a temporary pointer into it. If this pointer escapes from the closure and is used outside it, memory corruption may occur.

Recommendation

Do not use temporary pointers outside the closure they are passed to.

Example

In the first example below, the pointer is returned from the closure, potentially leading to memory corruption. In the second example, all work with the pointer is done inside the closure, and it is not returned.

func bad() {
    let ints = [1,2,3]
    let bytes = ints.withUnsafeBytes{
        return $0
    }
    print(bytes)
}

func good() {
    let ints = [1,2,3]
    let bytes = ints.withUnsafeBytes{
        print($0)
    }
}

References

exists(CallExpr call |
isUnsafePointerCall(call) and
f.getAnAccess() = call.getArgument(0).getExpr()
f.getAnAccess() = call.getAnArgument().getExpr()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it safe that this can match arguments other than the intended closure? If it's possible to pass a closure or function to another argument that is.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might need to revisit this if there's a case where the function takes multiple closures, but everything I've modeled only takes one closure argument.

ints.withUnsafeBytes({(p: UnsafeRawBufferPointer) in
v.field = p
return 1
}) // BAD

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the issue with implicit returns? Surely we will see them in real world code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue is that we were picking up the implicit return of the assignment, but not the field being assigned. This is currently not detected... I think there's some missing flow for closure captures.

* @kind path-problem
* @id swift/unsafe-pointer-escapes-closure
* @tags security
* external/cwe/cwe-825

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs precision, severity and security-severity tags. I guess we want to test with a near-final version before choosing a precision.


<references>
<li><a href="https://developer.apple.com/documentation/swift/array/withunsafebytes(_:)">withUnsafeBytes</a></li>
</references>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants