]> git.ipfire.org Git - thirdparty/valgrind.git/commitdiff
Bug 505673 - Valgrind crashes with an internal error and SIGBUS when the guest tries...
authorPaul Floyd <pjfloyd@wanadoo.fr>
Sat, 19 Jul 2025 13:10:31 +0000 (15:10 +0200)
committerPaul Floyd <pjfloyd@wanadoo.fr>
Sat, 19 Jul 2025 13:10:31 +0000 (15:10 +0200)
This is all quite messy.

It affects open() openat() and openat2() (the last of which is Linux only).
On Linux we also need to check for /proc/self/exe and /proc/PID/exe.
On Linux there are also a couple of RESOLVE flags for openat2() that
mean _don't_ check /proc magic links.
In the general case we need to have some reference to check whether
the filename matches the guest filename. So I've added that as
VG_(resolved_exename) (which I was already using on FreeBSD).
The pathname also needs to be canonicalised. It may be a
relative path, symlink or use RESOLVE_IN_ROOT. That uses
VG_(realpath) (again which was already present for FreBSD).
On illumos the man page says that opening running binaries for
writing failes with errno set to ETXTBSY but that's not what
the open functions do - they just open the file. So I've done nothing
for illumos or Solaris. Maybe I'll open an illumos ticket.
I haven't tried on Darwin.

The Linux open functions with /proc/self/exe and /proc/PID/exe
were just calling dup on the fd that we hold for the client exe.
That means that we were ignoring any other flags. That has now changed.
If the open doesn't fail because the WRONLY/RDWR flags are set then
the syscall gets called from the PRE wrapper using VG_(resolved_exename)
instewad of the /proc pathname.

I haven't tried to handle all of the Linux openat2 RESOLVE*
flags. RESOLVE_NO_MAGICLINKS is handled and I see the LTS test
openat202 now passing, so this should also fix Bug 506910.

I'm not sure that VG_(realpath) handles all forms of weird path
resolution on Linux (on FreeBSD it uses a syscall so that should
work OK).

19 files changed:
.gitignore
NEWS
coregrind/m_initimg/initimg-freebsd.c
coregrind/m_initimg/initimg-linux.c
coregrind/m_libcfile.c
coregrind/m_syswrap/syswrap-freebsd.c
coregrind/m_syswrap/syswrap-generic.c
coregrind/m_syswrap/syswrap-linux.c
coregrind/pub_core_libcfile.h
include/pub_tool_libcfile.h
memcheck/tests/x86-linux/scalar_openat2.stderr.exp
none/tests/freebsd/Makefile.am
none/tests/freebsd/open_client.cpp [new file with mode: 0644]
none/tests/freebsd/open_client.stderr.exp [new file with mode: 0644]
none/tests/freebsd/open_client.vgtest [new file with mode: 0644]
none/tests/linux/Makefile.am
none/tests/linux/open_client.cpp [new file with mode: 0644]
none/tests/linux/open_client.stderr.exp [new file with mode: 0644]
none/tests/linux/open_client.vgtest [new file with mode: 0644]

index 69e182f757395e4bc22f222b5451cdd71063e825..44454d08bb32e02de2daabe52f378a779a31595f 100644 (file)
 /none/tests/linux/mremap4
 /none/tests/linux/mremap5
 /none/tests/linux/mremap6
+/none/tests/linux/open_client
 /none/tests/linux/pthread-stack
 /none/tests/linux/stack-overflow
 
@@ -2328,6 +2329,7 @@ none/tests/freebsd/bug499212
 /none/tests/freebsd/swapcontext
 /none/tests/freebsd/fexecve
 /none/tests/freebsd/hello_world
+/none/tests/freebsd/open_client
 /none/tests/freebsd/proc_pid_file
 /none/tests/freebsd/sanity_level_thread
 /none/tests/freebsd/usrstack
diff --git a/NEWS b/NEWS
index e61b56baf9ea7a3953a9a7c0b931aa8eabad7f48..af88103641ab2cb2fae01982ba6ec1b43b788c26 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -52,6 +52,8 @@ are not entered into bugzilla tend to get forgotten about or ignored.
         AMD64_GET_TLSBASE
 505228  Wrap linux specific mseal syscall
 502968  Wrap linux specific syscalls 457 (listmount) and 458 (statmount)
+505673  Valgrind crashes with an internal error and SIGBUS when
+        the guest tries to open its own file with O_WRONLY|O_CREAT|O_TRUNC
 506076  unimplemented fcntl command: 1028 (F_CREATED_QUERY)
 506499  Unhandled syscall 592 (exterrctl - FreeBSD
 506795  Better report which clone flags are problematic
index cb98eb91445688ede8d75ff81864490fa077b8a0..5c167e3fb14a3d3917a7101bc55d0b64a421fc34 100644 (file)
@@ -334,6 +334,9 @@ static const struct auxv *find_auxv(const UWord* sp)
    return (const struct auxv *)sp;
 }
 
+/*
+ * @todo PJF Make this multi-platform
+ */
 static Bool try_get_interp(const HChar* args_exe, HChar* interp_out)
 {
    HChar  hdr[4096];
index 483b7a3ddd11e2ddc92c5180df82547ea24a1e7c..bcca59da74356ca13bb9ab45731760cff50e9518 100644 (file)
@@ -385,9 +385,59 @@ struct auxv *find_auxv(UWord* sp)
    return (struct auxv *)sp;
 }
 
+/*
+ * @todo PJF Make this multi-platform
+ */
+static Bool try_get_interp(const HChar* args_exe, HChar* interp_out)
+{
+   HChar  hdr[4096];
+   Int    len = sizeof hdr;
+   SysRes res;
+   Int fd;
+   HChar* end;
+   HChar* cp;
+   HChar* interp;
+
+   res = VG_(open)(args_exe, VKI_O_RDONLY, 0);
+   if (sr_isError(res)) {
+      return False;
+   } else {
+      fd = sr_Res(res);
+   }
+
+   res = VG_(pread)(fd, hdr, len, 0);
+
+   if (sr_isError(res)) {
+      VG_(close)(fd);
+      return False;
+   } else {
+      len = sr_Res(res);
+   }
+
+   if (0 != VG_(memcmp)(hdr, "#!", 2)) {
+      VG_(close)(fd);
+      return False;
+   }
+
+   end    = hdr + len;
+   interp = hdr + 2;
+   while (interp < end && (*interp == ' ' || *interp == '\t'))
+      interp++;
+
+   for (cp = interp; cp < end && !VG_(isspace)(*cp); cp++)
+      ;
+
+   *cp = '\0';
+
+   VG_(sprintf)(interp_out, "%s", interp);
+
+   VG_(close)(fd);
+   return True;
+}
+
 static 
 Addr setup_client_stack( void*  init_sp,
-                         HChar** orig_envp, 
+                         HChar** orig_envp,
                          const ExeInfo* info,
                          UInt** client_auxv,
                          Addr   clstack_end,
@@ -953,6 +1003,17 @@ Addr setup_client_stack( void*  init_sp,
 
    vg_assert((strtab-stringbase) == stringsize);
 
+   if (VG_(resolved_exename) == NULL) {
+      const HChar *exe_name = VG_(find_executable)(VG_(args_the_exename));
+      HChar interp_name[VKI_PATH_MAX];
+      if (try_get_interp(exe_name, interp_name)) {
+         exe_name = interp_name;
+      }
+      HChar resolved_name[VKI_PATH_MAX];
+      VG_(realpath)(exe_name, resolved_name);
+      VG_(resolved_exename) = VG_(strdup)("initimg-linux.sre.1", resolved_name);
+   }
+
    /* client_SP is pointing at client's argc/argv */
 
    if (0) VG_(printf)("startup SP = %#lx\n", client_SP);
index 767f34522c9d40f76f3261016e5fcbfdc1d4307e..ff1ead4e71cc93ac682587cea99681ad5a25a489 100644 (file)
@@ -674,15 +674,18 @@ Int VG_(fstat) ( Int fd, struct vg_stat* vgbuf )
 #  endif
 }
 
-#if defined(VGO_freebsd)
 /* extend this to other OSes as and when needed */
 SysRes VG_(lstat) ( const HChar* file_name, struct vg_stat* vgbuf )
 {
    SysRes res;
    VG_(memset)(vgbuf, 0, sizeof(*vgbuf));
 
-#if (__FreeBSD_version < 1200031)
+#if !defined(VGO_freebsd) || (__FreeBSD_version < 1200031)
+#if defined(VGO_freebsd)
    struct vki_freebsd11_stat buf;
+#else
+   struct vki_stat buf;
+#endif
    res = VG_(do_syscall2)(__NR_lstat, (UWord)file_name, (UWord)&buf);
 #else
    struct vki_stat buf;
@@ -693,7 +696,6 @@ SysRes VG_(lstat) ( const HChar* file_name, struct vg_stat* vgbuf )
    }
    return res;
 }
-#endif
 
 #undef TRANSLATE_TO_vg_stat
 #undef TRANSLATE_statx_TO_vg_stat
@@ -1803,7 +1805,6 @@ const HChar *VG_(dirname)(const HChar *path)
    return buf;
 }
 
-#if defined(VGO_freebsd)
 /*
  * I did look at nicking this from FreeBSD, it's fairly easy to port
  * but I was put off by the copyright and 3-clause licence
@@ -1819,7 +1820,7 @@ Bool VG_(realpath)(const HChar *path, HChar *resolved)
 {
    vg_assert(path);
    vg_assert(resolved);
-#if (__FreeBSD_version >= 1300080)
+#if defined(VGO_freebsd) && (__FreeBSD_version >= 1300080)
    return !sr_isError(VG_(do_syscall5)(__NR___realpathat, VKI_AT_FDCWD, (RegWord)path, (RegWord)resolved, VKI_PATH_MAX, 0));
 #else
    // poor man's realpath
@@ -1847,7 +1848,13 @@ Bool VG_(realpath)(const HChar *path, HChar *resolved)
       if (resolved_name[0] == '.' && resolved_name[1] == '/') {
          resolved_name += 2;
       }
-      VG_(snprintf)(resolved, VKI_PATH_MAX, "%s/%s", VG_(get_startup_wd)(), resolved_name);
+      HChar wd[VKI_PATH_MAX];
+#if defined(VGO_linux) || defined(VGO_solaris)
+      res = VG_(do_syscall2)(__NR_getcwd, (UWord)wd, VKI_PATH_MAX);
+#elif defined(VGO_freebsd)
+      res = VG_(do_syscall2)(__NR___getcwd, (UWord)wd, VKI_PATH_MAX);
+#endif
+      VG_(snprintf)(resolved, VKI_PATH_MAX, "%s/%s", wd, resolved_name);
    } else {
       VG_(snprintf)(resolved, VKI_PATH_MAX, "%s", resolved_name);
    }
@@ -1855,7 +1862,6 @@ Bool VG_(realpath)(const HChar *path, HChar *resolved)
    return True;
 #endif
 }
-#endif
 
 
 /*--------------------------------------------------------------------*/
index 79e30f7d37ee04174e81d280eb24d9f067ec1d2c..df223a2a05697a4467db1682c1080036ce488136 100644 (file)
@@ -5347,6 +5347,39 @@ PRE(sys_freebsd11_mknodat)
 // int openat(int fd, const char *path, int flags, ...);
 PRE(sys_openat)
 {
+   // check that we are not trying to open the client exe for writing
+   if ((ARG3 & VKI_O_WRONLY) ||
+       (ARG3 & VKI_O_RDWR)) {
+      vg_assert(VG_(resolved_exename) && VG_(resolved_exename)[0] == '/');
+      Int fd = ARG1;
+      const HChar* path = (const HChar*)ARG2;
+      if (ML_(safe_to_deref)(path, 1)) { // we need something like a "ML_(safe_to_deref_path)" that does a binary search for the addressable length, and maybe nul
+         if (fd  == VKI_AT_FDCWD) {
+            HChar tmp[VKI_PATH_MAX];
+            if (VG_(realpath)(path, tmp)) {
+               if (!VG_(strcmp)(tmp, VG_(resolved_exename))) {
+                     SET_STATUS_Failure( VKI_ETXTBSY );
+               }
+            }
+         } else {
+            const HChar* dirname;
+            if (VG_(resolve_filename)(fd, &dirname) == False) {
+               goto no_client_write; // let the OS do the error handling
+            }
+            HChar tmp1[VKI_PATH_MAX];
+            VG_(snprintf)(tmp1, VKI_PATH_MAX, "%s/%s", dirname, path);
+            tmp1[VKI_PATH_MAX - 1] = '\0';
+            //VG_(free)((void*)dirname);
+            HChar tmp2[VKI_PATH_MAX];
+            if (VG_(realpath)(tmp1, tmp2)) {
+               if (!VG_(strcmp)(tmp2, VG_(resolved_exename))) {
+                     SET_STATUS_Failure( VKI_ETXTBSY );
+               }
+            }
+         }
+      }
+   }
+no_client_write:
    if (ARG3 & VKI_O_CREAT) {
       // 4-arg version
       PRINT("sys_openat ( %" FMT_REGWORD "u, %#" FMT_REGWORD "x(%s), %" FMT_REGWORD "u, %" FMT_REGWORD "u )",ARG1,ARG2,(char*)ARG2,ARG3,ARG4);
index 1b3b5e80d6785727bf06b4fb481620d07d2fe39b..727766869ff57491881bf88795a0a0f7876c67c3 100644 (file)
@@ -4602,8 +4602,23 @@ Bool ML_(handle_self_exe_open)(SyscallStatus *status, const HChar *filename,
 }
 #endif // defined(VGO_linux)
 
+// int open(const char *path, int flags, mode_t mode);
 PRE(sys_open)
 {
+#if defined(VGO_linux)
+   HChar  name[30];   // large enough
+   Bool   proc_self_exe = False;
+
+   /* Check for /proc/self/exe or /proc/<pid>/exe case
+    * first so that we can then use the later checks. */
+   VG_(sprintf)(name, "/proc/%d/exe", VG_(getpid)());
+   if (ML_(safe_to_deref)( (void*)(Addr)ARG1, 1 )
+       && (VG_(strcmp)((HChar *)(Addr)ARG1, name) == 0
+           || VG_(strcmp)((HChar *)(Addr)ARG1, "/proc/self/exe") == 0)) {
+      proc_self_exe = True;
+   }
+#endif
+
    if (ARG2 & VKI_O_CREAT) {
       // 3-arg version
       PRINT("sys_open ( %#" FMT_REGWORD "x(%s), %ld, %ld )",ARG1,
@@ -4619,13 +4634,39 @@ PRE(sys_open)
    }
    PRE_MEM_RASCIIZ( "open(filename)", ARG1 );
 
+   // check that we are not trying to open the client exe for writing
+   if ((ARG2 & VKI_O_WRONLY) ||
+       (ARG2 & VKI_O_RDWR)) {
+#if defined(VGO_linux)
+      if (proc_self_exe) {
+
+         SET_STATUS_Failure( VKI_ETXTBSY );
+         return;
+      } else {
+#endif
+      vg_assert(VG_(resolved_exename));
+      const HChar* path = (const HChar*)ARG1;
+      if (ML_(safe_to_deref)(path, 1)) {
+         // we need something like a "ML_(safe_to_deref_path)" that does a binary search for the addressable length, and maybe nul
+         HChar tmp[VKI_PATH_MAX];
+         if (VG_(realpath)(path, tmp)) {
+            if (!VG_(strcmp)(tmp, VG_(resolved_exename))) {
+               SET_STATUS_Failure( VKI_ETXTBSY );
+               return;
+            }
+         }
+      }
+#if defined(VGO_linux)
+      }
+#endif
+   }
+
 #if defined(VGO_linux)
    /* Handle the case where the open is of /proc/self/cmdline or
       /proc/<pid>/cmdline, and just give it a copy of the fd for the
       fake file we cooked up at startup (in m_main).  Also, seek the
       cloned fd back to the start. */
    {
-      HChar  name[30];   // large enough
       HChar* arg1s = (HChar*) (Addr)ARG1;
       SysRes sres;
 
@@ -4648,6 +4689,11 @@ PRE(sys_open)
    if (ML_(handle_auxv_open)(status, (const HChar *)(Addr)ARG1, ARG2)
        || ML_(handle_self_exe_open)(status, (const HChar *)(Addr)ARG1, ARG2))
       return;
+
+   if (proc_self_exe) {
+      // do the syscall with VG_(resolved_exename)
+      SET_STATUS_from_SysRes(VG_(do_syscall3)(SYSNO, (Word)VG_(resolved_exename), ARG2, ARG3));
+   }
 #endif // defined(VGO_linux)
 
    /* Otherwise handle normally */
index bd563d9894cb85e27b90d9c5c636d43584cfa192..e16d293cd08f09a6c52172cf064a8cb71dc2461a 100644 (file)
@@ -5966,25 +5966,89 @@ PRE(sys_openat)
 {
    HChar  name[30];   // large enough
    SysRes sres;
+   Bool   proc_self_exe = False;
+
+   /* Check for /proc/self/exe or /proc/<pid>/exe case
+    * first so that we can then use the later checks. */
+   VG_(sprintf)(name, "/proc/%d/exe", VG_(getpid)());
+   if (ML_(safe_to_deref)( (void*)(Addr)ARG2, 1 )
+       && (VG_(strcmp)((HChar *)(Addr)ARG2, name) == 0
+           || VG_(strcmp)((HChar *)(Addr)ARG2, "/proc/self/exe") == 0)) {
+      proc_self_exe = True;
+   }
 
    if (ARG3 & VKI_O_CREAT) {
       // 4-arg version
       PRINT("sys_openat ( %ld, %#" FMT_REGWORD "x(%s), %ld, %ld )",
             SARG1, ARG2, (HChar*)(Addr)ARG2, SARG3, SARG4);
       PRE_REG_READ4(long, "openat",
-                    int, dfd, const char *, filename, int, flags, int, mode);
+                    int, dirfd, const char *, pathname, int, flags, int, mode);
    } else {
       // 3-arg version
       PRINT("sys_openat ( %ld, %#" FMT_REGWORD "x(%s), %ld )",
             SARG1, ARG2, (HChar*)(Addr)ARG2, SARG3);
       PRE_REG_READ3(long, "openat",
-                    int, dfd, const char *, filename, int, flags);
+                    int, dirfd, const char *, pathname, int, flags);
    }
 
-   PRE_MEM_RASCIIZ( "openat(filename)", ARG2 );
+   PRE_MEM_RASCIIZ( "openat(pathname)", ARG2 );
 
-   /* For absolute filenames, dfd is ignored.  If dfd is AT_FDCWD,
-      filename is relative to cwd.  When comparing dfd against AT_FDCWD,
+   // check that we are not trying to open the client exe for writing
+   if ((ARG3 & VKI_O_WRONLY) ||
+       (ARG3 & VKI_O_RDWR)) {
+      if (proc_self_exe) {
+         SET_STATUS_Failure( VKI_ETXTBSY );
+         return;
+      } else {
+         vg_assert(VG_(resolved_exename));
+         if (!VG_(strncmp)(VG_(resolved_exename), "#!", 2)) {
+            goto no_client_write;
+         }
+         Int fd = ARG1;
+         const HChar* path = (const HChar*)ARG2;
+         if (ML_(safe_to_deref)(path, 1)) {
+            // we need something like a "ML_(safe_to_deref_path)" that does a binary search for the addressable length, and maybe nul
+            if (path[0] == '/') {
+               // ignore fd
+               HChar tmp[VKI_PATH_MAX];
+               if (VG_(realpath)(path, tmp)) {
+                  if (!VG_(strcmp)(tmp, VG_(resolved_exename))) {
+                     SET_STATUS_Failure( VKI_ETXTBSY );
+                     return;
+                  }
+               }
+            } else {
+               if (fd  == VKI_AT_FDCWD) {
+                  HChar tmp[VKI_PATH_MAX];
+                  if (VG_(realpath)(path, tmp)) {
+                     if (!VG_(strcmp)(tmp, VG_(resolved_exename))) {
+                        SET_STATUS_Failure( VKI_ETXTBSY );
+                     }
+                  }
+               } else {
+                  const HChar* dirname;
+                  if (VG_(resolve_filename)(fd, &dirname) == False) {
+                     goto no_client_write; // let the OS do the error handling
+                  }
+                  HChar tmp1[VKI_PATH_MAX];
+                  VG_(snprintf)(tmp1, VKI_PATH_MAX, "%s/%s", dirname, path);
+                  tmp1[VKI_PATH_MAX - 1] = '\0';
+                  HChar tmp2[VKI_PATH_MAX];
+                  if (VG_(realpath)(tmp1, tmp2)) {
+                     if (!VG_(strcmp)(tmp2, VG_(resolved_exename))) {
+                        SET_STATUS_Failure( VKI_ETXTBSY );
+                        return;
+                     }
+                  }
+               }
+            }
+         }
+      }
+   }
+no_client_write:
+
+   /* For absolute filenames, dirfd is ignored.  If dirfd is AT_FDCWD,
+      filename is relative to cwd.  When comparing dirfd against AT_FDCWD,
       be sure only to compare the bottom 32 bits. */
    if (ML_(safe_to_deref)( (void*)(Addr)ARG2, 1 )
        && *(Char *)(Addr)ARG2 != '/'
@@ -6027,20 +6091,10 @@ PRE(sys_openat)
       return;
    }
 
-   /* And for /proc/self/exe or /proc/<pid>/exe case. */
+   if (proc_self_exe) {
 
-   VG_(sprintf)(name, "/proc/%d/exe", VG_(getpid)());
-   if (ML_(safe_to_deref)( (void*)(Addr)ARG2, 1 )
-       && (VG_(strcmp)((HChar *)(Addr)ARG2, name) == 0 
-           || VG_(strcmp)((HChar *)(Addr)ARG2, "/proc/self/exe") == 0)) {
-      sres = VG_(dup)( VG_(cl_exec_fd) );
-      SET_STATUS_from_SysRes( sres );
-      if (!sr_isError(sres)) {
-         OffT off = VG_(lseek)( sr_Res(sres), 0, VKI_SEEK_SET );
-         if (off < 0)
-            SET_STATUS_Failure( VKI_EMFILE );
-      }
-      return;
+      // do the syscall with VG_(resolved_exename)
+      SET_STATUS_from_SysRes(VG_(do_syscall4)(SYSNO, ARG1, (Word)VG_(resolved_exename), ARG3, ARG4));
    }
 
    /* Otherwise handle normally */
@@ -13900,43 +13954,109 @@ PRE(sys_openat2)
 {
    HChar  name[30];   // large enough
    SysRes sres;
-   struct vki_open_how * how;
+   struct vki_open_how* how = (struct vki_open_how *)ARG3;
+   Bool   proc_self_exe = False;
+   Bool   can_deref_how = how && ML_(safe_to_deref)(how, sizeof(*how));
+
+   // ARG4 is supposed to be sizeof(struct vki_open_how)
+   // but we can't trust it
+   if (can_deref_how) {
+      // check that we are not trying to open the client exe for writing
+      // this doesn't handle all of the RESOLVE options, there may be cases
+      // like RESOLVE_NO_XDEV and RESOLVE_BENEATH where the path is
+      // invalid and we might return the wrong errno
+      if (how->vki_resolve != VKI_RESOLVE_IN_ROOT &&
+          how->vki_resolve != VKI_RESOLVE_NO_MAGICLINKS) {
+         /* Check for /proc/self/exe or /proc/<pid>/exe case
+          * first so that we can then use the later checks. */
+         VG_(sprintf)(name, "/proc/%d/exe", VG_(getpid)());
+         if (ML_(safe_to_deref)( (void*)(Addr)ARG2, 1 )
+             && (VG_(strcmp)((HChar *)(Addr)ARG2, name) == 0
+                 || VG_(strcmp)((HChar *)(Addr)ARG2, "/proc/self/exe") == 0)) {
+            proc_self_exe = True;
+         }
+      }
+   }
 
    PRINT("sys_openat2 ( %ld, %#" FMT_REGWORD "x(%s), %#" FMT_REGWORD "x, %ld )",
             SARG1, ARG2, (HChar*)(Addr)ARG2, ARG3, SARG4);
    PRE_REG_READ4(long, "openat2",
-                    int, dfd, const char *, filename, struct vki_open_how *, how, vki_size_t, size);
+                    int, dirfd, const char *, pathname, struct vki_open_how *, how, vki_size_t, size);
 
-   PRE_MEM_RASCIIZ( "openat2(filename)", ARG2 );
+   PRE_MEM_RASCIIZ( "openat2(pathname)", ARG2 );
    PRE_MEM_READ( "openat2(how)", ARG3, sizeof(struct vki_open_how));
 
-   /* For absolute filenames, dfd is ignored.  If dfd is AT_FDCWD,
-      filename is relative to cwd.  When comparing dfd against AT_FDCWD,
+   if (can_deref_how &&
+       ((how->vki_flags & VKI_O_WRONLY) ||
+       (how->vki_flags & VKI_O_RDWR))) {
+      if (proc_self_exe) {
+         SET_STATUS_Failure( VKI_ETXTBSY );
+         return;
+      } else {
+         vg_assert(VG_(resolved_exename));
+         if (!VG_(strncmp)(VG_(resolved_exename), "#!", 2)) {
+            goto no_client_write;
+         }
+         Int fd = ARG1;
+         const HChar* path = (const HChar*)ARG2;
+         if (ML_(safe_to_deref)(path, 1)) {
+            // we need something like a "ML_(safe_to_deref_path)" that does a binary search for the addressable length, and maybe nul
+            if (path[0] == '/' && how->vki_resolve != VKI_RESOLVE_IN_ROOT) {
+               // absolute path, ignore fd
+               HChar tmp[VKI_PATH_MAX];
+               if (VG_(realpath)(path, tmp)) {
+                  if (!VG_(strcmp)(tmp, VG_(resolved_exename))) {
+                     SET_STATUS_Failure( VKI_ETXTBSY );
+                     return;
+                  }
+               }
+            } else {
+               // relative or rooted path
+               // or RESOLVE_IN_ROOT
+               if (how->vki_resolve == VKI_RESOLVE_IN_ROOT) {
+                  while (*path == '/' && *path != '\0') {
+                     ++path;
+                  }
+               }
+               if (fd  == VKI_AT_FDCWD) {
+                  HChar tmp[VKI_PATH_MAX];
+                  if (VG_(realpath)(path, tmp)) {
+                     if (!VG_(strcmp)(tmp, VG_(resolved_exename))) {
+                        SET_STATUS_Failure( VKI_ETXTBSY );
+                        return;
+                     }
+                  }
+               } else {
+                  // build absolute path from fd and path
+                  const HChar* dirname;
+                  if (VG_(resolve_filename)(fd, &dirname) == False) {
+                     goto no_client_write; // let the OS do the error handling
+                  }
+                  HChar tmp1[VKI_PATH_MAX];
+                  VG_(snprintf)(tmp1, VKI_PATH_MAX, "%s/%s", dirname, path);
+                  tmp1[VKI_PATH_MAX - 1] = '\0';
+                  HChar tmp2[VKI_PATH_MAX];
+                  if (VG_(realpath)(tmp1, tmp2)) {
+                     if (!VG_(strcmp)(tmp2, VG_(resolved_exename))) {
+                        SET_STATUS_Failure( VKI_ETXTBSY );
+                        return;
+                     }
+                  }
+               }
+            }
+         }
+      }
+   }
+ no_client_write:
+
+   /* For absolute filenames, dirfd is ignored.  If dirfd is AT_FDCWD,
+      filename is relative to cwd.  When comparing dirfd against AT_FDCWD,
       be sure only to compare the bottom 32 bits. */
    if (ML_(safe_to_deref)( (void*)(Addr)ARG2, 1 )
        && *(Char *)(Addr)ARG2 != '/'
        && ((Int)ARG1) != ((Int)VKI_AT_FDCWD)
-       && !ML_(fd_allowed)(ARG1, "openat2", tid, False))
+       && !ML_(fd_allowed)(ARG1, "openat", tid, False))
       SET_STATUS_Failure( VKI_EBADF );
-
-   how = (struct vki_open_how *)ARG3;
-
-   if (how && ML_(safe_to_deref) (how, sizeof(struct vki_open_how))) {
-      if (how->vki_mode) {
-         if (!(how->vki_flags & ((vki_uint64_t)VKI_O_CREAT | VKI_O_TMPFILE))) {
-            SET_STATUS_Failure( VKI_EINVAL );
-         }
-      }
-      if (how->vki_resolve & ~((vki_uint64_t)VKI_RESOLVE_NO_XDEV |
-                            VKI_RESOLVE_NO_MAGICLINKS |
-                            VKI_RESOLVE_NO_SYMLINKS |
-                            VKI_RESOLVE_BENEATH |
-                            VKI_RESOLVE_IN_ROOT |
-                            VKI_RESOLVE_CACHED)) {
-          SET_STATUS_Failure( VKI_EINVAL );
-      }
-   }
-
    /* Handle the case where the open is of /proc/self/cmdline or
       /proc/<pid>/cmdline, and just give it a copy of the fd for the
       fake file we cooked up at startup (in m_main).  Also, seek the
@@ -13972,20 +14092,10 @@ PRE(sys_openat2)
       return;
    }
 
-   /* And for /proc/self/exe or /proc/<pid>/exe case. */
+   if (proc_self_exe) {
 
-   VG_(sprintf)(name, "/proc/%d/exe", VG_(getpid)());
-   if (ML_(safe_to_deref)( (void*)(Addr)ARG2, 1 )
-       && (VG_(strcmp)((HChar *)(Addr)ARG2, name) == 0
-           || VG_(strcmp)((HChar *)(Addr)ARG2, "/proc/self/exe") == 0)) {
-      sres = VG_(dup)( VG_(cl_exec_fd) );
-      SET_STATUS_from_SysRes( sres );
-      if (!sr_isError(sres)) {
-         OffT off = VG_(lseek)( sr_Res(sres), 0, VKI_SEEK_SET );
-         if (off < 0)
-            SET_STATUS_Failure( VKI_EMFILE );
-      }
-      return;
+      // do the syscall with VG_(resolved_exename)
+      SET_STATUS_from_SysRes(VG_(do_syscall4)(SYSNO, ARG1, (Word)VG_(resolved_exename), ARG3, ARG4));
    }
 
    /* Otherwise handle normally */
index dc243bf7fe803b2e7577ad17bf86870d1cf38498..8411ad3687e021241cba69e0d12a9c31dcdb0066 100644 (file)
@@ -110,9 +110,8 @@ extern Int VG_(mkstemp) ( const HChar* part_of_name, /*OUT*/HChar* fullname );
    return if the working directory couldn't be found.  */
 extern void VG_(record_startup_wd) ( void );
 
-#if defined(VGO_freebsd)
+/* Resolves a path to a canonical absolute path */
 extern Bool VG_(realpath)(const HChar *path, HChar *resolved);
-#endif
 
 #endif   // __PUB_CORE_LIBCFILE_H
 
index ff4c0d3a5768f0f8c260b19a051ae7e3d9afe69d..5b912d31c1597d9560340289419fc67f73f17c4f 100644 (file)
@@ -81,9 +81,7 @@ extern Int    VG_(pipe)   ( Int fd[2] );
 extern Off64T VG_(lseek)  ( Int fd, Off64T offset, Int whence );
 
 extern SysRes VG_(stat)   ( const HChar* file_name, struct vg_stat* buf );
-#if defined(VGO_freebsd)
 extern SysRes VG_(lstat)  ( const HChar* file_name, struct vg_stat* buf );
-#endif
 extern Int    VG_(fstat)  ( Int   fd,        struct vg_stat* buf );
 extern SysRes VG_(dup)    ( Int oldfd );
 extern SysRes VG_(dup2)   ( Int oldfd, Int newfd );
index 367da9dbf259795c74a3593f345879c76fe6b22a..c666f9fb484c16e1aea22ea07c4e37c8a02ff1c5 100644 (file)
@@ -1,11 +1,11 @@
 -----------------------------------------------------
 437:        __NR_openat2 4s 2m
 -----------------------------------------------------
-Syscall param openat2(dfd) contains uninitialised byte(s)
+Syscall param openat2(dirfd) contains uninitialised byte(s)
    ...
    by 0x........: main (scalar_openat2.c:15)
 
-Syscall param openat2(filename) contains uninitialised byte(s)
+Syscall param openat2(pathname) contains uninitialised byte(s)
    ...
    by 0x........: main (scalar_openat2.c:15)
 
@@ -17,7 +17,7 @@ Syscall param openat2(size) contains uninitialised byte(s)
    ...
    by 0x........: main (scalar_openat2.c:15)
 
-Syscall param openat2(filename) points to unaddressable byte(s)
+Syscall param openat2(pathname) points to unaddressable byte(s)
    ...
    by 0x........: main (scalar_openat2.c:15)
  Address 0x........ is not stack'd, malloc'd or (recently) free'd
index 5d209c6d27ef094556536e5e7b4c480af5abb682..0bbbffdad6c2a3d07ae9ec5207ba91ba4ff436ae 100644 (file)
@@ -53,6 +53,8 @@ EXTRA_DIST = \
        ksh_test.ksh \
        ksh_test.stderr.exp \
        ksh_test.stdout.exp \
+       open_client.vgtest \
+       open_client.stderr.exp \
        sanity_level_thread.vgtest \
        sanity_level_thread.stderr.exp \
        swapcontext.vgtest \
@@ -69,7 +71,7 @@ EXTRA_DIST = \
        usrstack.stdout.exp
 
 check_PROGRAMS = \
-       auxv bug452274 bug498317 bug499212 fexecve hello_world osrel \
+       auxv bug452274 bug498317 bug499212 fexecve hello_world open_client osrel \
         proc_pid_file sanity_level_thread swapcontext umtx_shm_creat usrstack
 
 AM_CFLAGS   += $(AM_FLAG_M3264_PRI)
@@ -80,7 +82,7 @@ osrel_CFLAGS = ${AM_CFLAGS}
 swapcontext_CFLAGS = ${AM_CFLAGS}
 
 hello_world_SOURCES = hello_world.cpp
-
+open_client_SOURCES = open_client.cpp
 proc_pid_file_SOURCES = proc_pid_file.cpp
 
 sanity_level_thread_SOURCES = sanity_level_thread.cpp
diff --git a/none/tests/freebsd/open_client.cpp b/none/tests/freebsd/open_client.cpp
new file mode 100644 (file)
index 0000000..454c0a3
--- /dev/null
@@ -0,0 +1,108 @@
+// For Bug 505673
+// Valgrind crashes with an internal error and SIGBUS when the guest tries to open its own file with O_WRONLY|O_CREAT|O_TRUNC
+#include <fcntl.h>
+#include <cerrno>
+#include <stdexcept>
+#include <vector>
+#include <unistd.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+int main(int argc, char** argv)
+{
+    std::vector<int> flags{O_WRONLY|O_CREAT|O_TRUNC, O_WRONLY, O_RDWR};
+
+    // On FreeBSD libc open uses syscall openat (at least on 14.2)
+    for (auto f : flags)
+    {
+        int res = open(argv[0], f, 0666);
+        if (-1 != res)
+        {
+            throw std::runtime_error("open should have failed");
+        }
+        else
+        {
+            if (errno != ETXTBSY)
+            {
+                throw std::runtime_error("errno should be ETXTBSY");
+            }
+        }
+    }
+
+    // repeat the above, but with syscall SYS_open
+    for (auto f : flags)
+    {
+        int res = syscall(SYS_open, argv[0], f, 0666);
+        if (-1 != res)
+        {
+            throw std::runtime_error("open should have failed");
+        }
+        else
+        {
+            if (errno != ETXTBSY)
+            {
+                throw std::runtime_error("errno should be ETXTBSY");
+            }
+        }
+    }
+
+    int dotdot;
+    if ((dotdot = open("..", O_DIRECTORY | O_RDONLY)) == -1)
+    {
+        throw std::runtime_error("failed to open ,.");
+    }
+    else
+    {
+        for (auto f : flags)
+        {
+            int res = openat(dotdot, "freebsd/open_client", f, 0666);
+            if (-1 != res)
+            {
+                throw std::runtime_error("open should have failed");
+            }
+            else
+            {
+                if (errno != ETXTBSY)
+                {
+                    throw std::runtime_error("errno should be ETXTBSY");
+                }
+            }
+        }
+    }
+    close(dotdot);
+
+    chdir("..");
+
+    // check that relative paths work
+    for (auto f : flags)
+    {
+        int res = open("freebsd/open_client", f, 0666);
+        if (-1 != res)
+        {
+            throw std::runtime_error("open should have failed");
+        }
+        else
+        {
+            if (errno != ETXTBSY)
+            {
+                throw std::runtime_error("errno should be ETXTBSY");
+            }
+        }
+    }
+
+    for (auto f : flags)
+    {
+        int res = syscall(SYS_open, "freebsd/open_client", f, 0666);
+        if (-1 != res)
+        {
+            throw std::runtime_error("open should have failed");
+        }
+        else
+        {
+            if (errno != ETXTBSY)
+            {
+                throw std::runtime_error("errno should be ETXTBSY");
+            }
+        }
+    }
+}
diff --git a/none/tests/freebsd/open_client.stderr.exp b/none/tests/freebsd/open_client.stderr.exp
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/none/tests/freebsd/open_client.vgtest b/none/tests/freebsd/open_client.vgtest
new file mode 100644 (file)
index 0000000..12b148d
--- /dev/null
@@ -0,0 +1,3 @@
+prog: open_client
+vgopts: -q
+
index dbb535902e698009bfb050c11521e37288eae45d..c81ffff5488507e21db8230497f9b25e7c044000 100644 (file)
@@ -17,6 +17,7 @@ EXTRA_DIST = \
        mremap4.stderr.exp mremap4.vgtest \
        mremap5.stderr.exp mremap5.vgtest \
        mremap6.stderr.exp mremap6.vgtest \
+       open_client.stderr.exp open_client.vgtest \
        pthread-stack.stderr.exp pthread-stack.vgtest \
        stack-overflow.stderr.exp stack-overflow.vgtest
 
@@ -32,6 +33,7 @@ check_PROGRAMS = \
        mremap4 \
        mremap5 \
        mremap6 \
+       open_client \
        pthread-stack \
        stack-overflow
 
@@ -45,6 +47,7 @@ AM_CXXFLAGS += $(AM_FLAG_M3264_PRI)
 
 # Special needs
 clonev_LDADD = -lpthread
+open_client_SOURCES = open_client.cpp
 pthread_stack_LDADD = -lpthread
 
 stack_overflow_CFLAGS = $(AM_CFLAGS) @FLAG_W_NO_UNINITIALIZED@ \
diff --git a/none/tests/linux/open_client.cpp b/none/tests/linux/open_client.cpp
new file mode 100644 (file)
index 0000000..4b336e7
--- /dev/null
@@ -0,0 +1,191 @@
+// For Bug 505673
+// Valgrind crashes with an internal error and SIGBUS when the guest tries to open its own file with O_WRONLY|O_CREAT|O_TRUNC
+#include <fcntl.h>
+#include <cerrno>
+#include <stdexcept>
+#include <string>
+#include <vector>
+#include <unistd.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+#include <linux/openat2.h>
+
+int main(int argc, char** argv)
+{
+    std::vector<__u64> flags{O_WRONLY|O_CREAT|O_TRUNC, O_WRONLY, O_RDWR};
+    auto pid = getpid();
+    auto ppe = std::string("/proc/") + std::to_string(pid) + "/exe";
+    std::vector<std::string> names{argv[0], "/proc/self/exe", ppe};
+    int dotdot;
+
+    for (const auto& n : names)
+    {
+        for (auto f : flags)
+        {
+            int res = open(n.c_str(), f, 0666);
+            if (-1 != res)
+            {
+                throw std::runtime_error("open should have failed");
+            }
+            else
+            {
+                if (errno != ETXTBSY)
+                {
+                    throw std::runtime_error("errno should be ETXTBSY");
+                }
+            }
+        }
+
+        for (auto f : flags)
+        {
+            int res = syscall(SYS_open, n.c_str(), f, 0666);
+
+            if (-1 != res)
+            {
+                throw std::runtime_error("open should have failed");
+            }
+            else
+            {
+                if (errno != ETXTBSY)
+                {
+                    throw std::runtime_error("errno should be ETXTBSY");
+                }
+            }
+        }
+    }
+
+    if ((dotdot = open("..", O_DIRECTORY | O_RDONLY)) == -1)
+    {
+        throw std::runtime_error("failed to open ,.");
+    }
+    else
+    {
+        for (auto f : flags)
+        {
+            int res = openat(dotdot, "linux/open_client", f, 0666);
+            if (-1 != res)
+            {
+                throw std::runtime_error("open should have failed");
+            }
+            else
+            {
+                if (errno != ETXTBSY)
+                {
+                    throw std::runtime_error("errno should be ETXTBSY");
+                }
+            }
+        }
+    }
+
+#if defined(SYS_openat2)
+    for (const auto& n : names)
+    {
+        for (auto f : flags)
+        {
+            struct open_how oh = { .flags=f, .mode=((f&static_cast<__u64>(O_CREAT))?0666UL:0UL), .resolve=0 };
+            int res = syscall(SYS_openat2, AT_FDCWD, n.c_str(), &oh, sizeof(oh));
+            if (-1 != res)
+            {
+                throw std::runtime_error("openat2 should have failed");
+            }
+            else
+            {
+                if (errno != ETXTBSY)
+                {
+                    throw std::runtime_error("errno should be ETXTBSY");
+                }
+            }
+        }
+    }
+
+    for (auto f : flags)
+    {
+        struct open_how oh = { .flags=f, .mode=((f&static_cast<__u64>(O_CREAT))?0666UL:0UL), .resolve=0 };
+        int res = syscall(SYS_openat2, dotdot, "linux/open_client", &oh, sizeof(oh));
+        if (-1 != res)
+        {
+            throw std::runtime_error("openat2 should have failed");
+        }
+        else
+        {
+            if (errno != ETXTBSY)
+            {
+                throw std::runtime_error("errno should be ETXTBSY");
+            }
+        }
+    }
+
+    for (auto f : flags)
+    {
+        struct open_how oh = { .flags=f, .mode=((f&static_cast<__u64>(O_CREAT))?0666UL:0UL), .resolve=RESOLVE_IN_ROOT };
+        int res = syscall(SYS_openat2, dotdot, "/linux/open_client", &oh, sizeof(oh));
+        if (-1 != res)
+        {
+            throw std::runtime_error("openat2 should have failed");
+        }
+        else
+        {
+            if (errno != ETXTBSY)
+            {
+                throw std::runtime_error("errno should be ETXTBSY");
+            }
+        }
+    }
+#endif
+
+    close(dotdot);
+
+    chdir("..");
+
+    // check that relative paths work
+    for (auto f : flags)
+    {
+        int res = open("linux/open_client", f, 0666);
+        if (-1 != res)
+        {
+            throw std::runtime_error("open should have failed");
+        }
+        else
+        {
+            if (errno != ETXTBSY)
+            {
+                throw std::runtime_error("errno should be ETXTBSY");
+            }
+        }
+    }
+
+    for (auto f : flags)
+    {
+        int res = syscall(SYS_open, "linux/open_client", f, 0666);
+        if (-1 != res)
+        {
+            throw std::runtime_error("open should have failed");
+        }
+        else
+        {
+            if (errno != ETXTBSY)
+            {
+                throw std::runtime_error("errno should be ETXTBSY");
+            }
+        }
+    }
+
+#if defined(SYS_openat2)
+    for (auto f : flags)
+    {
+        struct open_how oh = { .flags=f, .mode=((f&static_cast<__u64>(O_CREAT))?0666UL:0UL), .resolve=0 };
+        int res = syscall(SYS_openat2, AT_FDCWD, "linux/open_client", &oh, sizeof(oh));
+        if (-1 != res)
+        {
+            throw std::runtime_error("openat2 should have failed");
+        }
+        else
+        {
+            if (errno != ETXTBSY)
+            {
+                throw std::runtime_error("errno should be ETXTBSY");
+            }
+        }
+    }
+#endif
+}
diff --git a/none/tests/linux/open_client.stderr.exp b/none/tests/linux/open_client.stderr.exp
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/none/tests/linux/open_client.vgtest b/none/tests/linux/open_client.vgtest
new file mode 100644 (file)
index 0000000..6b930ef
--- /dev/null
@@ -0,0 +1,2 @@
+prog: open_client
+vgopts: -q