Skip to content
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

fix(patch): memory unsafe file read on Linux #234

Merged
merged 3 commits into from Mar 13, 2024

Conversation

jpgrayson
Copy link
Contributor

Description

When reading from the file descriptor into the temporary buffer, it was only by happenstance that the buffer would contain a terminating nul byte.

The buffer is uninitialized and thus may contain any data.

And the /proc files that are read into the buffer are not nul terminated.

If the temporary buffer happened to be zero-initialized, then this would accidentally work correctly.

If the temporary buffer happened to have a nul byte somewhere after the read data and in-bounds, then the end of the resulting String would be corrupted with arbitrary crud. This crud would ostensibly be after a terminating newline and thus parsers might have ignored it?

If the temporary buffer happened to not have any nul bytes after the read data, then other Bad Things could happen, including silent success or a crash due to segmentation fault or various checks in String being violated.

The repair is to append a nul byte after the read data (ensuring that the nul byte is not placed after the end of the buffer).

What motivated me to go look for this issue is that I have a benchmark suite in a non-public project that crashes due to this issue. That benchmark suite runs lots of iterations and somehow seems to have a memory access pattern that regularly leads to the conditions where the ill-effects of this bug can manifest.

How Has This Been Tested?

Regarding testing, I did some one-off testing that pre-filled the temporary buffer with non-zero bytes and then observed those bytes were erroneously present in the resulting String.

I have not attempted to craft a unit test that triggers this bug. That would require either directly or indirectly manipulating the system allocator to affect the initial state of this temporary buffer. Too much effort, IMO.

Minimal checklist:

  • I have performed a self-review of my own code
  • I have added DocC code-level documentation for any public interfaces exported by the package
  • I have added unit and/or integration tests that prove my fix is effective or that my feature works

jpgrayson and others added 2 commits March 12, 2024 19:06
When reading from the file descriptor into the temporary buffer, it was
only by happenstance that the buffer would contain a terminating nul
byte.

The buffer is uninitialized and thus may contain any data.

And the `/proc` files that are read into the buffer are **not** nul
terminated.

If the temporary buffer happened to be zero-initialized, then this would
accidentally work correctly.

If the temporary buffer happend to have a nul byte somewhere after the
read data and in-bounds, then the end of the resulting String would be
corrupted with arbitrary crud. This crud would ostensibly be after a
terminating newline and thus parsers might have ignored it?

If the temporary buffer happened to not have any nul bytes after the
read data, then other Bad Things could happen, including silent success
or a crash due to segmentation fault or various checks in String being
violated.

The repair is to append a nul byte after the read data (ensuring that
the nul byte is not placed after the end of the buffer).

Signed-off-by: Peter Grayson <pete@jpgrayson.net>
@hassila
Copy link
Contributor

hassila commented Mar 13, 2024

Great catch, thank you very much! Added an extra precondition for now as well if more data than should be possible comes in.

@hassila hassila changed the title fix: memory unsafe file read on Linux fix(patch): memory unsafe file read on Linux Mar 13, 2024
Copy link

codecov bot commented Mar 13, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 65.03%. Comparing base (81e2c4d) to head (309fd8b).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #234      +/-   ##
==========================================
+ Coverage   64.99%   65.03%   +0.04%     
==========================================
  Files          33       33              
  Lines        3462     3466       +4     
==========================================
+ Hits         2250     2254       +4     
  Misses       1212     1212              
Files Coverage Δ
...stemStats/OperatingSystemStatsProducer+Linux.swift 96.30% <100.00%> (+0.11%) ⬆️
Files Coverage Δ
...stemStats/OperatingSystemStatsProducer+Linux.swift 96.30% <100.00%> (+0.11%) ⬆️

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 81e2c4d...309fd8b. Read the comment docs.

@hassila
Copy link
Contributor

hassila commented Mar 13, 2024

The CI failures on Linux/5.7 are really weird, initializeElement(at have been around for a very long time and should definitely be available, and on macOS we now have a compiler crash (unrelated to this PR). I'll go ahead and merge this regardless, as it's fixing a crash bug and 5.7 will be dropped quite soon anyway.

@hassila hassila merged commit e495d16 into ordo-one:main Mar 13, 2024
11 of 15 checks passed
@hassila
Copy link
Contributor

hassila commented Mar 13, 2024

Thanks for the contribution!

@jpgrayson
Copy link
Contributor Author

Thank you for taking a look at this PR so quickly, @hassila. Much appreciated!

@jpgrayson
Copy link
Contributor Author

The added precondition that checks for buffer overrun is unnecessary. The FileDescriptor.read() function indicates that it ensures the read fits within the provided buffer pointer.

https://swiftpackageindex.com/apple/swift-system/main/documentation/systempackage/filedescriptor/read(into:retryoninterrupt:)#discussion

Also, as written the precondition(count < maxReadBuffer) would incorrectly fail if the read used all the buffer; i.e. count would equal maxReadBuffer. So a <= test would be better (but I suggest just removing the precondition entirely).

@jpgrayson jpgrayson deleted the fix/memory-unsafe-file-read-on-linux branch March 13, 2024 15:38
@hassila
Copy link
Contributor

hassila commented Mar 13, 2024

If count would equal max buffer there might be more data available we didn't get, so it was on purpose it was written thus - in practice we should never hit that, then something is quite unexpected.

@jpgrayson
Copy link
Contributor Author

Ah, I better understand the intent now. Thanks for explaining.

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

Successfully merging this pull request may close these issues.

None yet

2 participants