Dear Maintainer,
I tried to debug this issue.

    (gdb) bt
    #0  tidy_path (path=0x55f0c81ea140 <abspath> "/../") at symlinks.c:96
    #1  0x000055f0c7fe6908 in fix_symlink (my_dev=2049, path=0x55f0c81ec1e0 
<path> "/tmp/tmp.tlVVwIgOZQ/mylink") at symlinks.c:185
    #2  0x000055f0c7fe6e81 in dirwalk (pathlen=<optimized out>, dev=2049, 
path=0x55f0c81ec1e0 <path> "/tmp/tmp.tlVVwIgOZQ/mylink") at symlinks.c:290
    #3  0x000055f0c7fe5f1d in main (argc=<optimized out>, argv=0x7ffd5d3596f0) 
at symlinks.c:376


I guess I found a buffer underrun in following lines:

    76      static int tidy_path (char *path)
    ...
    91              while ((p = strstr(path,"/../")) != NULL) {
    92                      s = p+3;
    93                      for (p--; p != path; p--) if (*p == '/') break;

In line 93 p equals path already but is decremented before the
condition 'p != path' is checked the first time.
Therefore this loop is just ended when the first '/' is found
before the begin of the path variable, which is "luckily" in our
case in the .rodata section of the executable, to that we want
to write in line 96.

This modification makes symlinks not crash and shows plausible output:

    -               for (p--; p != path; p--) if (*p == '/') break;
    +               for (p--; p > path; p--) if (*p == '/') break;

Kind regards,
Bernhard
# Buster/stable amd64 qemu VM 2020-02-16


apt update
apt dist-upgrade

apt install systemd-coredump mc fakeroot symlinks gdb symlinks-dbgsym
apt build-dep symlinks




mkdir /home/benutzer/source/symlinks/orig -p
cd    /home/benutzer/source/symlinks/orig
apt source symlinks
cd




cat <<EOF > reproduce.sh
#! /usr/bin/env sh
MYTMP=\$(mktemp -d)
ln -s /.. \$MYTMP/mylink
symlinks \$MYTMP
EOF

chmod +x reproduce.sh
./reproduce.sh

journalctl --no-pager

coredumpctl list

coredumpctl gdb 1443

set width 0
set pagination off
directory /home/benutzer/source/symlinks/orig/symlinks-1.4
bt







###########
###########
###########





benutzer@debian:~$ ./reproduce.sh 
Segmentation fault (core dumped)



root@debian:~# journalctl --no-pager
...
Feb 17 23:49:17 debian kernel: symlinks[1443]: segfault at 55f0c81e70ed ip 
000055f0c7fe675a sp 00007ffd5d359360 error 7 in symlinks[55f0c7fe5000+3000]
Feb 17 23:49:17 debian kernel: Code: 1d 0f 1f 80 00 00 00 00 80 3a 2f 74 11 48 
83 ea 01 48 39 d5 75 f2 48 89 ea 80 3a 2f 75 29 31 c9 0f b6 74 08 03 bb 01 00 
00 00 <40> 88 34 0a 48 83 c1 01 40 84 f6 75 e9 4c 89 e6 48 89 ef e8 0e f6
Feb 17 23:49:17 debian systemd[1]: Created slice 
system-systemd\x2dcoredump.slice.
Feb 17 23:49:17 debian systemd[1]: Started Process Core Dump (PID 1444/UID 0).
Feb 17 23:49:17 debian systemd-coredump[1445]: Process 1443 (symlinks) of user 
1000 dumped core.
                                               
                                               Stack trace of thread 1443:
                                               #0  0x000055f0c7fe675a n/a 
(symlinks)
                                               #1  0x000055f0c7fe6908 n/a 
(symlinks)
                                               #2  0x000055f0c7fe6e81 n/a 
(symlinks)
                                               #3  0x000055f0c7fe5f1d n/a 
(symlinks)
                                               #4  0x00007fea02a9f09b 
__libc_start_main (libc.so.6)
                                               #5  0x000055f0c7fe61da n/a 
(symlinks)
Feb 17 23:49:17 debian systemd[1]: systemd-coredump@0-1444-0.service: Succeeded.


https://wiki.debian.org/InterpretingKernelOutputAtProcessCrash
    error 7 == 0b111
 *   bit 0 ==    1: protection fault
 *   bit 1 ==    1: write access
 *   bit 2 ==    1: user-mode access





root@debian:~# coredumpctl list
TIME                            PID   UID   GID SIG COREFILE  EXE
Mon 2020-02-17 23:49:17 CET    1443  1000  1000  11 present   /usr/bin/symlinks





root@debian:~# coredumpctl gdb 1443
           PID: 1443 (symlinks)
           UID: 1000 (benutzer)
           GID: 1000 (benutzer)
        Signal: 11 (SEGV)
     Timestamp: Mon 2020-02-17 23:49:17 CET (1min 25s ago)
  Command Line: symlinks /tmp/tmp.tlVVwIgOZQ
    Executable: /usr/bin/symlinks
 Control Group: /user.slice/user-1000.slice/session-3.scope
          Unit: session-3.scope
         Slice: user-1000.slice
       Session: 3
     Owner UID: 1000 (benutzer)
       Boot ID: 2d5d5576a57947a7aad042da5f907289
    Machine ID: 33f18f39d2a9438eb75b0ed52848afcd
      Hostname: debian
       Storage: 
/var/lib/systemd/coredump/core.symlinks.1000.2d5d5576a57947a7aad042da5f907289.1443.1581979757000000.lz4
       Message: Process 1443 (symlinks) of user 1000 dumped core.
                
                Stack trace of thread 1443:
                #0  0x000055f0c7fe675a n/a (symlinks)
                #1  0x000055f0c7fe6908 n/a (symlinks)
                #2  0x000055f0c7fe6e81 n/a (symlinks)
                #3  0x000055f0c7fe5f1d n/a (symlinks)
                #4  0x00007fea02a9f09b __libc_start_main (libc.so.6)
                #5  0x000055f0c7fe61da n/a (symlinks)

GNU gdb (Debian 8.2.1-2+b3) 8.2.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /usr/bin/symlinks...(no debugging symbols found)...done.
[New LWP 1443]
Core was generated by `symlinks /tmp/tmp.tlVVwIgOZQ'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x000055f0c7fe675a in ?? ()
(gdb) set width 0
(gdb) set pagination off
(gdb) bt
#0  0x000055f0c7fe675a in ?? ()
#1  0x000055f0c7fe6908 in ?? ()
#2  0x000055f0c7fe6e81 in ?? ()
#3  0x000055f0c7fe5f1d in ?? ()
#4  0x00007fea02a9f09b in __libc_start_main (main=0x55f0c7fe5da0, argc=2, 
argv=0x7ffd5d3596e8, init=<optimized out>, fini=<optimized out>, 
rtld_fini=<optimized out>, stack_end=0x7ffd5d3596d8) at ../csu/libc-start.c:308
#5  0x000055f0c7fe61da in ?? ()




Core was generated by `symlinks /tmp/tmp.tlVVwIgOZQ'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  tidy_path (path=0x55f0c81ea140 <abspath> "/../") at symlinks.c:96
96      symlinks.c: Datei oder Verzeichnis nicht gefunden.
(gdb) set width 0
(gdb) set pagination off
(gdb) directory /home/benutzer/source/symlinks/orig/symlinks-1.4
Source directories searched: 
/home/benutzer/source/symlinks/orig/symlinks-1.4:$cdir:$cwd
(gdb) bt
#0  tidy_path (path=0x55f0c81ea140 <abspath> "/../") at symlinks.c:96
#1  0x000055f0c7fe6908 in fix_symlink (my_dev=2049, path=0x55f0c81ec1e0 <path> 
"/tmp/tmp.tlVVwIgOZQ/mylink") at symlinks.c:185
#2  0x000055f0c7fe6e81 in dirwalk (pathlen=<optimized out>, dev=2049, 
path=0x55f0c81ec1e0 <path> "/tmp/tmp.tlVVwIgOZQ/mylink") at symlinks.c:290
#3  0x000055f0c7fe5f1d in main (argc=<optimized out>, argv=0x7ffd5d3596f0) at 
symlinks.c:376



(gdb) list symlinks.c:96
warning: Source file is more recent than executable.
91              while ((p = strstr(path,"/../")) != NULL) {
92                      s = p+3;
93                      for (p--; p != path; p--) if (*p == '/') break;
94                      if (*p != '/')
95                              break;
96                      while ((*p++ = *s++));
97                      tidied = 1;
98              }
99              if (*path == '\0')
100                     strcpy(path,"/");


(gdb) bt full
#0  tidy_path (path=0x55f0c81ea140 <abspath> "/../") at symlinks.c:96
        tidied = <optimized out>
        s = 0x55f0c81ea144 <abspath+4> ""
        p = 0x55f0c81e70ee ""
#1  0x000055f0c7fe6908 in fix_symlink (my_dev=2049, path=0x55f0c81ec1e0 <path> 
"/tmp/tmp.tlVVwIgOZQ/mylink") at symlinks.c:185
        p = <optimized out>
        np = <optimized out>
        lp = <optimized out>
        tail = <optimized out>
        msg = <optimized out>
        stbuf = {st_dev = 18446744073709551615, st_ino = 18446744073709551615, 
st_nlink = 18446744073709551615, st_mode = 4294967295, st_uid = 4294967295, 
st_gid = 0, __pad0 = 0, st_rdev = 0, st_size = 0, st_blksize = 0, st_blocks = 
0, st_atim = {tv_sec = 0, tv_nsec = 0}, st_mtim = {tv_sec = 0, tv_nsec = 0}, 
st_ctim = {tv_sec = 0, tv_nsec = 0}, __glibc_reserved = {0, 0, 0}}
        lstbuf = {st_dev = 0, st_ino = 0, st_nlink = 0, st_mode = 0, st_uid = 
0, st_gid = 0, __pad0 = 0, st_rdev = 0, st_size = 0, st_blksize = 0, st_blocks 
= 0, st_atim = {tv_sec = 0, tv_nsec = 8}, st_mtim = {tv_sec = 1581979757, 
tv_nsec = 336592469}, st_ctim = {tv_sec = 1581979757, tv_nsec = 336592469}, 
__glibc_reserved = {1581979757, 336592469, 0}}
        c = <optimized out>
        fix_abs = 0
        fix_messy = 0
        fix_long = 0
        lpath = "/..", '\000' <repeats 4092 times>
        new = '\000' <repeats 4095 times>
        abspath = "/../", '\000' <repeats 4091 times>
#2  0x000055f0c7fe6e81 in dirwalk (pathlen=<optimized out>, dev=2049, 
path=0x55f0c81ec1e0 <path> "/tmp/tmp.tlVVwIgOZQ/mylink") at symlinks.c:290
        name = 0x55f0c81ec1f4 <path+20> "mylink"
        dfd = 0x55f0c9f4f280
        st = {st_dev = 2049, st_ino = 131382, st_nlink = 1, st_mode = 41471, 
st_uid = 1000, st_gid = 1000, __pad0 = 0, st_rdev = 0, st_size = 3, st_blksize 
= 4096, st_blocks = 0, st_atim = {tv_sec = 1581979757, tv_nsec = 336592469}, 
st_mtim = {tv_sec = 1581979757, tv_nsec = 336592469}, st_ctim = {tv_sec = 
1581979757, tv_nsec = 336592469}, __glibc_reserved = {0, 0, 0}}
        dp = <optimized out>
        __s1_len = <optimized out>
        __s2_len = <optimized out>
        __s2 = <optimized out>
        __result = <optimized out>
        __s1_len = <optimized out>
        __s2_len = <optimized out>
        __s2 = <optimized out>
        __result = <optimized out>
#3  0x000055f0c7fe5f1d in main (argc=<optimized out>, argv=0x7ffd5d3596f0) at 
symlinks.c:376
        st = {st_dev = 2049, st_ino = 131381, st_nlink = 2, st_mode = 16832, 
st_uid = 1000, st_gid = 1000, __pad0 = 0, st_rdev = 0, st_size = 4096, 
st_blksize = 4096, st_blocks = 8, st_atim = {tv_sec = 1581979757, tv_nsec = 
336592469}, st_mtim = {tv_sec = 1581979757, tv_nsec = 336592469}, st_ctim = 
{tv_sec = 1581979757, tv_nsec = 336592469}, __glibc_reserved = {0, 0, 0}}
        path = "/tmp/tmp.tlVVwIgOZQ/mylink", '\000' <repeats 4071 times>
        cwd = <optimized out>
        dircount = 0
        c = <optimized out>
        p = 0x7ffd5d35a84f "/tmp/tmp.tlVVwIgOZQ"
(gdb) 
(gdb) disassemble $pc-41,$pc+20
Dump of assembler code from 0x55f0c7fe6731 to 0x55f0c7fe676e:
   0x000055f0c7fe6731 <tidy_path+145>:  nopl   0x0(%rax)
   0x000055f0c7fe6738 <tidy_path+152>:  cmpb   $0x2f,(%rdx)
   0x000055f0c7fe673b <tidy_path+155>:  je     0x55f0c7fe674e <tidy_path+174>
   0x000055f0c7fe673d <tidy_path+157>:  sub    $0x1,%rdx
   0x000055f0c7fe6741 <tidy_path+161>:  cmp    %rdx,%rbp
   0x000055f0c7fe6744 <tidy_path+164>:  jne    0x55f0c7fe6738 <tidy_path+152>
   0x000055f0c7fe6746 <tidy_path+166>:  mov    %rbp,%rdx
   0x000055f0c7fe6749 <tidy_path+169>:  cmpb   $0x2f,(%rdx)
   0x000055f0c7fe674c <tidy_path+172>:  jne    0x55f0c7fe6777 <tidy_path+215>
   0x000055f0c7fe674e <tidy_path+174>:  xor    %ecx,%ecx
   0x000055f0c7fe6750 <tidy_path+176>:  movzbl 0x3(%rax,%rcx,1),%esi
   0x000055f0c7fe6755 <tidy_path+181>:  mov    $0x1,%ebx
=> 0x000055f0c7fe675a <tidy_path+186>:  mov    %sil,(%rdx,%rcx,1)
   0x000055f0c7fe675e <tidy_path+190>:  add    $0x1,%rcx
   0x000055f0c7fe6762 <tidy_path+194>:  test   %sil,%sil
   0x000055f0c7fe6765 <tidy_path+197>:  jne    0x55f0c7fe6750 <tidy_path+176>
   0x000055f0c7fe6767 <tidy_path+199>:  mov    %r12,%rsi
   0x000055f0c7fe676a <tidy_path+202>:  mov    %rbp,%rdi
   0x000055f0c7fe676d <tidy_path+205>:  callq  0x55f0c7fe5d80 <strstr@plt>
End of assembler dump.



(gdb) print/x $sil
$1 = 0x2f
(gdb) print/x $rdx
$2 = 0x55f0c81e70ed
(gdb) print/x $rcx
$3 = 0x0


(gdb) info target
Symbols from "/usr/bin/symlinks".
Local core dump file:
        `/var/tmp/coredump-LWP7Ur', file type elf64-x86-64.
...
        0x000055f0c81e7000 - 0x000055f0c81e8000 is load2

????



###########
###########
###########



gdb -q
set width 0
set pagination off
directory /home/benutzer/source/symlinks/orig/symlinks-1.4
file /usr/bin/symlinks
run /tmp/tmp.tlVVwIgOZQ
disassemble $pc-41,$pc+20
print/x $rdx
info target




benutzer@debian:~$ gdb -q
(gdb) set width 0
(gdb) set pagination off
(gdb) directory /home/benutzer/source/symlinks/orig/symlinks-1.4
Source directories searched: 
/home/benutzer/source/symlinks/orig/symlinks-1.4:$cdir:$cwd
(gdb) file /usr/bin/symlinks
Reading symbols from /usr/bin/symlinks...Reading symbols from 
/usr/lib/debug/.build-id/39/62acae57f4d516661920488e2cf5b3d856ade1.debug...done.
done.
(gdb) run /tmp/tmp.tlVVwIgOZQ
Starting program: /usr/bin/symlinks /tmp/tmp.tlVVwIgOZQ

Program received signal SIGSEGV, Segmentation fault.
tidy_path (path=0x555555759140 <abspath> "/../") at symlinks.c:96
warning: Source file is more recent than executable.
96                      while ((*p++ = *s++));


(gdb) bt full 2
#0  tidy_path (path=0x555555759140 <abspath> "/../") at symlinks.c:96
        tidied = <optimized out>
        s = 0x555555759144 <abspath+4> ""
        p = 0x5555557560ee ""
#1  0x0000555555555908 in fix_symlink (my_dev=2049, path=0x55555575b1e0 <path> 
"/tmp/tmp.tlVVwIgOZQ/mylink") at symlinks.c:185
        p = <optimized out>
        np = <optimized out>
        lp = <optimized out>
        tail = <optimized out>
        msg = <optimized out>
        stbuf = {st_dev = 18446744073709551615, st_ino = 18446744073709551615, 
st_nlink = 18446744073709551615, st_mode = 4294967295, st_uid = 4294967295, 
st_gid = 0, __pad0 = 0, st_rdev = 0, st_size = 280375465082880, st_blksize = 0, 
st_blocks = 0, st_atim = {tv_sec = 0, tv_nsec = 0}, st_mtim = {tv_sec = 0, 
tv_nsec = 0}, st_ctim = {tv_sec = 0, tv_nsec = 0}, __glibc_reserved = {0, 0, 0}}
        lstbuf = {st_dev = 0, st_ino = 0, st_nlink = 0, st_mode = 0, st_uid = 
0, st_gid = 0, __pad0 = 0, st_rdev = 0, st_size = 0, st_blksize = 0, st_blocks 
= 0, st_atim = {tv_sec = 0, tv_nsec = 8}, st_mtim = {tv_sec = 1581980596, 
tv_nsec = 801775324}, st_ctim = {tv_sec = 1581979757, tv_nsec = 336592469}, 
__glibc_reserved = {1581979757, 336592469, 0}}
        c = <optimized out>
        fix_abs = 0
        fix_messy = 0
        fix_long = 0
        lpath = "/..", '\000' <repeats 4092 times>
        new = '\000' <repeats 4095 times>
        abspath = "/../", '\000' <repeats 4091 times>
(More stack frames follow...)


(gdb) disassemble $pc-41,$pc+20
Dump of assembler code from 0x555555555731 to 0x55555555576e:
   0x0000555555555731 <tidy_path+145>:  nopl   0x0(%rax)
   0x0000555555555738 <tidy_path+152>:  cmpb   $0x2f,(%rdx)
   0x000055555555573b <tidy_path+155>:  je     0x55555555574e <tidy_path+174>
   0x000055555555573d <tidy_path+157>:  sub    $0x1,%rdx
   0x0000555555555741 <tidy_path+161>:  cmp    %rdx,%rbp
   0x0000555555555744 <tidy_path+164>:  jne    0x555555555738 <tidy_path+152>
   0x0000555555555746 <tidy_path+166>:  mov    %rbp,%rdx
   0x0000555555555749 <tidy_path+169>:  cmpb   $0x2f,(%rdx)
   0x000055555555574c <tidy_path+172>:  jne    0x555555555777 <tidy_path+215>
   0x000055555555574e <tidy_path+174>:  xor    %ecx,%ecx
   0x0000555555555750 <tidy_path+176>:  movzbl 0x3(%rax,%rcx,1),%esi
   0x0000555555555755 <tidy_path+181>:  mov    $0x1,%ebx
=> 0x000055555555575a <tidy_path+186>:  mov    %sil,(%rdx,%rcx,1)
   0x000055555555575e <tidy_path+190>:  add    $0x1,%rcx
   0x0000555555555762 <tidy_path+194>:  test   %sil,%sil
   0x0000555555555765 <tidy_path+197>:  jne    0x555555555750 <tidy_path+176>
   0x0000555555555767 <tidy_path+199>:  mov    %r12,%rsi
   0x000055555555576a <tidy_path+202>:  mov    %rbp,%rdi
   0x000055555555576d <tidy_path+205>:  callq  0x555555554d80 <strstr@plt>
End of assembler dump.
(gdb) print/x $rdx
$1 = 0x5555557560ed

(gdb) info target
Symbols from "/usr/bin/symlinks".
Native process:
        Using the running image of child process 4909.
        While running this, GDB does not access memory from...
Local exec file:
        `/usr/bin/symlinks', file type elf64-x86-64.
        Entry point: 0x5555555551b0
...
        0x0000555555555f20 - 0x000055555555617c is .rodata
...


--> We are not allowed to write to .rodata





(gdb) list 76,120
76      static int tidy_path (char *path)
77      {
78              int tidied = 0;
79              char *s, *p;
80
81              s = path + strlen(path) - 1;
82              if (s[0] != '/') {      /* tmp trailing slash simplifies things 
*/
83                      s[1] = '/';
84                      s[2] = '\0';
85              }
86              while (substr(path, "/./", "/"))
87                      tidied = 1;
88              while (substr(path, "//", "/"))
89                      tidied = 1;
90
91              while ((p = strstr(path,"/../")) != NULL) {
92                      s = p+3;
93                      for (p--; p != path; p--) if (*p == '/') break;         
        <<<<<<<<< p is decremented even if p == path, therefore we "search" a 
'/' before path begins
94                      if (*p != '/')
95                              break;
96                      while ((*p++ = *s++));
97                      tidied = 1;
98              }
99              if (*path == '\0')
100                     strcpy(path,"/");
101             p = path + strlen(path) - 1;
102             if (p != path && *p == '/')
103                     *p-- = '\0';    /* remove tmp trailing slash */
104             while (p != path && *p == '/') {        /* remove any others */
105                     *p-- = '\0';
106                     tidied = 1;
107             }
108             while (!strncmp(path,"./",2)) {
109                     for (p = path, s = path+2; (*p++ = *s++););
110                     tidied = 1;
111             }
112             return tidied;
113     }





gdb -q
set width 0
set pagination off
directory /home/benutzer/source/symlinks/orig/symlinks-1.4
file /usr/bin/symlinks
b tidy_path
run /tmp/tmp.tlVVwIgOZQ
display/i $pc
display p
display path
cont
record
cont
reverse-next





#########
#########
#########




--- symlinks-1.4.orig/symlinks.c
+++ symlinks-1.4/symlinks.c
@@ -90,7 +90,7 @@ static int tidy_path (char *path)
 
        while ((p = strstr(path,"/../")) != NULL) {
                s = p+3;
-               for (p--; p != path; p--) if (*p == '/') break;
+               for (p--; p > path; p--) if (*p == '/') break;
                if (*p != '/')
                        break;
                while ((*p++ = *s++));

                
                
benutzer@debian:~$ symlinks /tmp/tmp.tlVVwIgOZQ
absolute: /tmp/tmp.tlVVwIgOZQ/mylink -> /..

Reply via email to