-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Foundation: fix invalid free #2662
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
Conversation
@swift-ci please test Linux platform |
cc @phausler |
Foundation/NSData.swift
Outdated
@@ -602,7 +602,7 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding { | |||
let buffer = UnsafeMutableRawBufferPointer(start: ptr, count: capacity) | |||
let length = NSData.base64EncodeBytes(self, options: options, buffer: buffer) | |||
|
|||
return String(bytesNoCopy: ptr, length: length, encoding: .ascii, freeWhenDone: true)! | |||
return String(bytesNoCopy: ptr, length: length, encoding: .ascii, freeWhenDone: 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.
This makes a copy since it ends up calling:
public convenience init?(bytesNoCopy bytes: UnsafeMutableRawPointer, length len: Int, encoding: UInt, freeWhenDone freeBuffer: Bool) /* "NoCopy" is a hint */ {
// just copy for now since the internal storage will be a copy anyhow
self.init(bytes: bytes, length: len, encoding: encoding)
if freeBuffer { // don't take the hint
free(bytes)
}
}
So I think it will leak. If you are going to pass false
I think you will need to call ptr.deallocate()
after creating the string.
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.
Ugh, I missed the UnsafeMutableRawPointer.allocate
on L601. You are right, this is a leak. So, there something going on here thats triggering a ton of failures/heap corruption on Windows.
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.
Maybe there is an issue estimating the buffer size to use and its under allocating. I rewrote this code recently so I may have introduced a bug. You could try allocating capacity * 3
or something just to make sure the buffer is definitely big enough and see if you still see the issue.
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.
Just tested it locally, over-estimating the capacity does not seem to help
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.
Maybe this is a simpler fix:
@ -597,12 +597,14 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
let dataLength = self.length
if dataLength == 0 { return "" }
- let capacity = estimateBase64Size(length: dataLength)
+ let capacity = estimateBase64Size(length: dataLength) + 1
let ptr = UnsafeMutableRawPointer.allocate(byteCount: capacity, alignment: 1)
let buffer = UnsafeMutableRawBufferPointer(start: ptr, count: capacity)
let length = NSData.base64EncodeBytes(self, options: options, buffer: buffer)
-
- return String(bytesNoCopy: ptr, length: length, encoding: .ascii, freeWhenDone: true)!
+ buffer[length] = 0
+ let string = String(validatingUTF8: ptr.assumingMemoryBound(to: CChar.self))!
+ ptr.deallocate()
+ return string
}
/// Creates a Base64, UTF-8 encoded Data from the data object using the given options.
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.
Updated but using defer
for the ptr.deallocate()
.
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 wonder if this is ultimately a bug in the function NSString.init?(bytes: UnsafeRawPointer, length len: Int, encoding: UInt)
which is being called by the String(bytesNoCopy..
call as I cant see anything wrong with the original code.
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.
This reminds me of an old issue that never really got resolved - there is a difference between SwiftFoundation and Foundation where lengths between CFString
and NSData
are different due to the trailing null-terminator. This would fit that scenario - the backing here is NSData
rather than CFString
.
I think that is a separate issue to iron out and we should still merge this though.
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 agree that the issue is probably in the NSData
-> String
conversion, Im having a look at it now. Could we leave this unmerged for a bit as its the only current reproducer of the bug and would be good to see if it can be fixed properly.
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 found an issue with my fix so feel free to merge, I will see If I can find a longer timer solution.
8efa652
to
3534d99
Compare
@swift-ci please test Linux platform |
The computation of the base64encoding would trim the trailing nul terminator on the rendered string. This would result in invalid string manipulation leading to heap corruptions on Windows. Thanks to Simon Evans for spotting the issue and proposing the fix! (And a thank you for spotting a leak that I was accidentally introducing.)
3534d99
to
f3a6f50
Compare
@swift-ci please test Linux platform |
The memory in the
String
is not-being copied, and thus is more of aview into the memory, not an owning reference. Do not free this memory
after the destruction of the object.