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.
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.
(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? :-)
(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!
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.
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
Warner, do you have any plans for MFC or is this PR can be closed now? Thanks
I have no further plans for this bug. If others want to MFC, great.
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