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

overlapping buffers and memccpy() #78

Closed
krader1961 opened this issue Oct 17, 2017 · 5 comments
Closed

overlapping buffers and memccpy() #78

krader1961 opened this issue Oct 17, 2017 · 5 comments

Comments

@krader1961
Copy link
Contributor

krader1961 commented Oct 17, 2017

The change I authored, commit de00119, which was recently merged to fix building on BSD based systems like macOS only works when using gcc to do the compilation. If you instead use the clang compiler provided by Apple it fails because the BSD preprocessor symbol isn't defined. You also need to blacklist __APPLE__.

But here's the thing, the code before my change began with this preprocessor directive:

#if _lib_memccpy && !__ia64 /* these guys may never get it right */

The problem is that assertion is wrong. It implies that the libc implementation of memccpy() on IA64 is broken. Which is incorrect. The problem is the AST sfputr() function is depending on what is explicitly undefined behavior in the face of overlapping source and destination buffers. Every implementation is free to optimize the operation by assuming the buffers do not overlap but in so doing produce incorrect results, including aborting the program, if the buffers do overlap.

I was too timid with my previous change. I should not have added another blacklisted architecture. Instead the use of memccpy() should simply be removed. If you look at the two alternatives using memccpy() simply complicates the code and is unlikely to be measurably, let alone noticeably, faster.

@krader1961
Copy link
Contributor Author

I instrumented the code to write a diagnostic message when sfputr() is called with overlapping buffers. Many, but not all, show that the buffers have the same initial address:

WTF ps = 0x1e00a85e23b0, s = 0x1e00a85e23b0, p = 1024

Many others overlap somewhat:

WTF ps = 0x1e00a85e2432, s = 0x1e00a85e21df, p = 894

Note the p = value is decimal rather than hex. So those two buffers do overlap since 0x2432 + 894 is greater than 0x21df.

I have no idea why that function is being called with overlapping buffers. But it obviously broken by relying on memccpy() working correctly with overlapping buffers. How is it that this was not noticed long ago?

@krader1961
Copy link
Contributor Author

I did a more fine grained analysis. First, note that 97.2% of the calls to memccpy() in sfputr() move fewer than 100 bytes while building the entire AST project. And 99.6% move fewer than 500 bytes. So we really don't need the optimized implementation provided by memccpy(). The explicit loop that is already used if memccpy() doesn't handle overlapping buffers is plenty fast given typical inputs. Just for grins I bucketed the calls by the number of bytes copied using a bucket size of 1000:

17788687 1000
  20009 2000
   4508 3000
    354 4000
   1585 5000
   6840 6000
   2322 7000
   1453 8000
    224 9000
    112 10000
     16 11000
      1 12000
      3 13000
      1 15000
     29 17000
     71 18000
     40 19000
      2 21000
      2 24000
      1 25000
     98 33000
     98 34000
      2 41000
      1 47000
      2 135000

Also, when the source and destination buffers aren't the same (i.e., have different starting addrs) it appears they never actually overlap. It only appears they might because a large length (typically 1024) is being passed in. But because the copy terminates when a null byte is seen, and that almost always happens within the first 100 bytes, there isn't any overlap when the starting addr of each buffer differs. Of the invocations that theoretically have overlapping buffers only 27.4% actually overlap. And those are suspicious given that the source and destination addrs are the same and thus should be a nop. Meaning it should be okay to elide the copy.

@krader1961
Copy link
Contributor Author

Note that ksh cannot be built with the clang toolchain on macOS even after blacklisting defined(__APPLE__) from the systems which can use memccpy(). Not because of a run-time problem but because AST is still trying to support the C standard from circa 1980.

There are a significant number of problems with the code. Most notably with respect to functions which have more than one declaration which do not agree with each other. Something gcc is willing to ignore (apparently for compatibility with ancient C compilers) but clang is not willing to ignore. This does not surprise me very much given the uses of __STD_C in the code to determine whether function declarations have to conform to the original K&R C syntax or anything more modern.

@krader1961
Copy link
Contributor Author

krader1961 commented Oct 20, 2017

I just noticed issue #4 for which this issue is essentially a duplicate. My commit de00119 that was recently merged fixes both of the problems identified in issue #4 but in a slightly different manner than was proposed there. So issue #4 can be closed.

However, as noted in my previous comments on this issue sfputr() shouldn't be using memccpy() even on systems where it apparently works because doing so relies on undefined behavior which, theoretically, could change on those systems. The only reason I haven't proposed another change to eliminate that use of memccpy() is that doing so still doesn't yield a working ksh command when building with clang on macOS. I'm still debugging what else is broken but got side tracked by trying to figure out how to not build all the stuff in the AST project that is not needed to build ksh.

siteshwar pushed a commit that referenced this issue Oct 25, 2017
The behavior of the `memccpy()` function is undefined when the buffers
overlap which is sometimes true for the `sfputr()` function. So always
use an explicit loop to copy the data.

Fixes #78
@krader1961
Copy link
Contributor Author

Closing since my change to remove the unsafe use of memccpy() has been merged.

siteshwar pushed a commit that referenced this issue Nov 30, 2017
The behavior of the `memccpy()` function is undefined when the buffers
overlap which is sometimes true for the `sfputr()` function. So always
use an explicit loop to copy the data.

Fixes #78
McDutchie added a commit to ksh93/ksh that referenced this issue Jun 11, 2020
The sfputr() function (put out a null-terminated string) contained
a use of memccpy() that was invalid and could cause crashes,
because the buffer it was copying into could overlap or even be
identical with the buffer being copied from.

Among (probably) other things, this commit fixes a crash in 'print
-v' (print a compound variable structure) on macOS, that caused the
comvar.sh and comvario.sh regression tests to fail spectacularly.
Now they pass.

Issue discovered and fixed by Kurtis Rader in the abandoned ksh2020
branch; this commit backports the fix. He wrote:

| #if _lib_memccpy && !__ia64 /* these guys may never get it right */
|
| The problem is that assertion is wrong. It implies that the libc
| implementation of memccpy() on IA64 is broken. Which is
| incorrect. The problem is the AST sfputr() function is depending
| on what is explicitly undefined behavior in the face of
| overlapping source and destination buffers.
| [...] Using memccpy() simply complicates the code and is unlikely
| to be measurably, let alone noticeably, faster.

Further discussion/analysis: att#78

src/lib/libast/sfio/sfputr.c:
- Remove memccpy use. Always use the manual copying loop.

(cherry picked from commit fbe3c83335256cb714a4aa21f555083c9f1d71d8)
McDutchie added a commit to ksh93/ksh that referenced this issue Jun 11, 2020
The sfputr() function (put out a null-terminated string) contained
a use of memccpy() that was invalid and could cause crashes,
because the buffer it was copying into could overlap or even be
identical with the buffer being copied from.

Among (probably) other things, this commit fixes a crash in 'print
-v' (print a compound variable structure) on macOS, that caused the
comvar.sh and comvario.sh regression tests to fail spectacularly.
Now they pass.

Issue discovered and fixed by Kurtis Rader in the abandoned ksh2020
branch; this commit backports the fix. He wrote:

| #if _lib_memccpy && !__ia64 /* these guys may never get it right */
|
| The problem is that assertion is wrong. It implies that the libc
| implementation of memccpy() on IA64 is broken. Which is
| incorrect. The problem is the AST sfputr() function is depending
| on what is explicitly undefined behavior in the face of
| overlapping source and destination buffers.
| [...] Using memccpy() simply complicates the code and is unlikely
| to be measurably, let alone noticeably, faster.

Further discussion/analysis: att#78

src/lib/libast/sfio/sfputr.c:
- Remove memccpy use. Always use the manual copying loop.

(cherry picked from commit fbe3c83335256cb714a4aa21f555083c9f1d71d8)
citrus-it pushed a commit to citrus-it/ast that referenced this issue Apr 15, 2021
Some notes:
- Removed a TODO note that was fixed in commit 43d9fba.
- Removed a duplicate note about the '%l' time format in the changelog.
- Applied the following documentation fixes from Terrence J. Doyle:
  - https://www.mail-archive.com/ast-developers@lists.research.att.com/msg01852.html
  - https://www.mail-archive.com/ast-developers@lists.research.att.com/msg01856.html
- Fixed strange grammar in one of the error messages.
- Added missing options for rksh to the synopsis section.
- Applied a formatting fix from ksh93v- to the man page.
- Replaced a C99 line comment in src/lib/libast/comp/realpath.c with a
  proper comment that is valid in C89.
- Prioritize UTC over GMT in the documentation (missed by commit c9634e9).
- Add some extra information for 'ksh -R file' to the man page. This patch
  is from Red Hat: https://git.centos.org/rpms/ksh/blob/c8/f/SOURCES/ksh-20080202-manfix.patch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant