Bug 223653 - Possibly serious string manipulation surprise in -HEAD
Summary: Possibly serious string manipulation surprise in -HEAD
Status: Closed FIXED
Alias: None
Product: Base System
Classification: Unclassified
Component: arm (show other bugs)
Version: CURRENT
Hardware: arm64 Any
: --- Affects Some People
Assignee: freebsd-arm (Nobody)
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2017-11-13 15:38 UTC by karl
Modified: 2019-01-22 21:52 UTC (History)
5 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description karl 2017-11-13 15:38:40 UTC
Take the following tiny code snippet:

#include        <stdio.h>
#include        <string.h>

int     main(argc, argv)
int     argc;
char    *argv[];

{
        char    tmp[132];
        char    tmp2[132];

        strcpy(tmp, "00 01 02 03 04 05 06");

        printf("First: [%s]\n", tmp);

        strcpy(tmp, &tmp[3]);
        printf("Second: [%s]\n", tmp);

        strcpy(tmp, &tmp[3]);
        printf("Third: [%s]\n", tmp);

        strcpy(tmp, &tmp[3]);
        printf("Fourth: [%s]\n", tmp);

        strcpy(tmp, "00 01 02 03 04 05 06");

        printf("2-First: [%s]\n", tmp);

        strcpy(tmp2, &tmp[3]);
        strcpy(tmp, tmp2);
        printf("2-Second: [%s]\n", tmp);

        strcpy(tmp2, &tmp[3]);
        strcpy(tmp, tmp2);
        printf("2-Third: [%s]\n", tmp);

        strcpy(tmp2, &tmp[3]);
        strcpy(tmp, tmp2);
        printf("2-Fourth: [%s]\n", tmp);
}


Compile it on FreeBSD 12.0-CURRENT #0 r325754: Mon Nov 13 08:02:13 CST 2017 on an RPI3 and run it.

root@rpi3:/data/HD-MCP # ./test2
First: [00 01 02 03 04 05 06]
Second: [02 04 05 05 06 06]
Third: [04 05 05 06 06]
Fourth: [05 05 06 06]
2-First: [00 01 02 03 04 05 06]
2-Second: [01 02 03 04 05 06]
2-Third: [02 03 04 05 06]
2-Fourth: [03 04 05 06]
root@rpi3:/data/HD-MCP #

Yes, I'm aware that strcpy with overlapping strings is not safe in all instances (strcpy is of course unsafe if the source can be longer than where you are copying into) but historically it has been safe when used to remove some number of bytes from the left side ("front") of a string as the destination cannot overflow and on a byte-wise copy code that is generated it stops as expected after the trailing null is copied over.

There's also no note in the current man page that this is unsafe.  Rewriting the strcpys to use memmove corrects the above problem (memmove specifically is ok to use in all cases of overlapping storage of course) but this change is likely to bite a material number of people and has not been a problem on any architecture I've seen under FreeBSD until fairly recently (it did not cause problems with a February build of -HEAD on the RPI3/arm64, for example.)

I note that if I try to link OPENSSL into a piece of code I have here I get a trap immediately on an "illegal opcode" when I try to execute the resulting binary, which may be due to this happening somewhere in the initialization code.  I get no frame labels on the backtrace, and the trap occurs before main() starts executing (a breakpoint set on the first actual instruction in main() fails to be reached) so unfortunately I do not have a better trace on what's going on.  I suspect the behavior above, or some other artifact of the same change, is responsible for the below; the code in question has been running on an RPI2 (which right now I am using under FreeBSD 11.0-STABLE #1 r313159M) and various Intel (i386 and Amd64) architectures, also under 11-Stable, for a long period of time:

root@rpi3:/data/HD-MCP # lldb hd-mcp
(lldb) target create "hd-mcp"
Current executable set to 'hd-mcp' (aarch64).
(lldb) run -n
Process 924 launching
Process 924 launched: '/data/HD-MCP/hd-mcp' (aarch64)
Process 924 stopped
* thread #1, name = 'hd-mcp', stop reason = signal SIGILL: illegal trap
    frame #0: 0x00000000403342e8
->  0x403342e8: .long  0x0ee0e000                ; unknown opcode
    0x403342ec: ret
    0x403342f0: stp    x28, x19, [sp, #-0x20]!
    0x403342f4: stp    x29, x30, [sp, #0x10]
(lldb) bt
* thread #1, name = 'hd-mcp', stop reason = signal SIGILL: illegal trap
  * frame #0: 0x00000000403342e8
    frame #1: 0x0000000040082ad8
    frame #2: 0x0000000040081ab4
(lldb)

If I leave OpenSSL out (I have a "no security" version that #ifdef's it out) then the code compiles and runs fine.
Comment 1 Bob Bishop 2017-11-13 15:58:29 UTC
Ever since K&R second edition: "The behaviour is undefined if copying takes place between overlapping objects."

Would do no harm for the man page to say so.
Comment 2 karl 2017-11-13 16:23:44 UTC
(In reply to Bob Bishop from comment #1)

Yeah, I understand how it can happen of course (if you're moving words, for example, instead of bytes) but the man page makes no note that there's a problem with it..... 

I have gotten a reply on the list about the SIGILL trap, but I have a suspicion that a related problem with lldb may be connected to the above behavior -- if I try to print a value when in the debugger that is valid in the current frame the *debugger* traps on a SEGV.... 

Is lldb relying on strcpy with overlapping source and destination working? :-)
Comment 3 karl 2017-11-13 16:30:59 UTC
(In reply to karl from comment #2)

Example:

root@rpi3:/data/HD-MCP # lldb hd-mcp.freeware
(lldb) target create "hd-mcp.freeware"
Current executable set to 'hd-mcp.freeware' (aarch64).
(lldb) b 12752
Breakpoint 1: where = hd-mcp.freeware`main + 192 at hd-mcp.c:12752, address = 0x0000000000040974

(12751 is the first "real" assignment in main(); so stop right after the buffer is initialized)

(lldb) l 12751
   12751                x10_fail_event[0] = 0;
   12752                status_buffer[0] = 0;
   12753                status_mod = 0;
   12754
   12755                emit_html5_script[0] = 0;
   12756
   12757                int     dynamic_time;
   12758
   12759        #ifdef  OPENSSL
   12760                SSL     *ssl_socket;
(lldb) r -n
Process 1277 launching
Process 1277 launched: '/data/HD-MCP/hd-mcp.freeware' (aarch64)
Process 1277 stopped
* thread #1, name = 'hd-mcp.freeware', stop reason = breakpoint 1.1
    frame #0: 0x0000000000040974 hd-mcp.freeware`main(argc=2, argv=0x0000ffffffffebc8) at hd-mcp.c:12752
   12749
   12750
   12751                x10_fail_event[0] = 0;
-> 12752                status_buffer[0] = 0;
   12753                status_mod = 0;
   12754
   12755                emit_html5_script[0] = 0;
(lldb) p x10_fail_event
Segmentation fault (core dumped)
root@rpi3:/data/HD-MCP #

BOOM!
Comment 4 Warner Losh freebsd_committer freebsd_triage 2017-11-13 16:47:31 UTC
strcpy on overlapping strings is undefined, per ANSI standard since at least C89:

7.21.2.3 The strcpy function
Synopsis
1 #include <string.h>
char *strcpy(char * restrict s1,
const char * restrict s2);
Description
2 The strcpy function copies the string pointed to by s2 (including the terminating null character) into the array pointed to by s1. If copying takes place between objects that overlap, the behavior is undefined.

So this is a documentation bug.
Comment 5 commit-hook freebsd_committer freebsd_triage 2017-11-13 17:05:50 UTC
A commit references this bug:

Author: imp
Date: Mon Nov 13 17:04:45 UTC 2017
New revision: 325765
URL: https://svnweb.freebsd.org/changeset/base/325765

Log:
  Add notes about overlapping copies.

  Add notes to each of these that specifically state that results are
  undefined if the strings overlap. In the case of memcpy, we document
  the overlapping behavior on FreeBSD (pre-existing). For str*, it is
  left unspecified, however, since the default (and x86) implementations
  do not handle overlapping strings properly.

  PR: 223653
  Sponsored by: Netflix

Changes:
  head/lib/libc/string/memcpy.3
  head/lib/libc/string/strcat.3
  head/lib/libc/string/strcpy.3
Comment 6 Oleksandr Tymoshenko freebsd_committer freebsd_triage 2019-01-21 21:53:37 UTC
Warner, do you have any plans for MFC or is this PR can be closed now?

Thanks
Comment 7 Warner Losh freebsd_committer freebsd_triage 2019-01-22 06:24:14 UTC
I have no further plans for this bug. If others want to MFC, great.
Comment 8 commit-hook freebsd_committer freebsd_triage 2019-01-22 21:52:49 UTC
A commit references this bug:

Author: rgrimes
Date: Tue Jan 22 21:52:09 UTC 2019
New revision: 343326
URL: https://svnweb.freebsd.org/changeset/base/343326

Log:
  MFC: 325765 (imp) Add notes about overlapping copies.

  Add notes to each of these that specifically state that results are
  undefined if the strings overlap. In the case of memcpy, we document
  the overlapping behavior on FreeBSD (pre-existing). For str*, it is
  left unspecified, however, since the default (and x86) implementations
  do not handle overlapping strings properly.

  PR: 223653
  Approved by:	phk (mentor)

Changes:
_U  stable/10/
  stable/10/lib/libc/string/memcpy.3
  stable/10/lib/libc/string/strcat.3
  stable/10/lib/libc/string/strcpy.3
_U  stable/11/
  stable/11/lib/libc/string/memcpy.3
  stable/11/lib/libc/string/strcat.3
  stable/11/lib/libc/string/strcpy.3