Просмотр исходного кода

linuxthreads: fix SIGSEGV in MIPS cancellable wrappers (waitpid, nanosleep)

On MIPS (seen on mips64 n32, which uses LinuxThreads) almost every
dynamically linked test program crashed with SIGSEGV. The fault is in the
prologue of the cancellable waitpid wrapper, at the first GOT load
(lw t9, off(gp)): the computed gp points outside the mapped libc.

CANCELABLE_SYSCALL defines the wrapper with __attribute__((weak)), and
for waitpid/nanosleep libpthread_hidden_proto/_def turns that weak
function into the __GI_<name> symbol used internally. On MIPS a function
derives its PIC gp via %gp_rel(__GI_<name>) (the libc_hidden_proto
redirect). __GI_<name> is also defined weak by the libc fallback in
libc/sysdeps/linux/common/<name>.c (CANCELLABLE_SYSCALL +
libc_hidden_weak for the LinuxThreads build). With two weak __GI_<name>
the linker coalesces them to one address -- the fallback's, not the
wrapper's -- so the wrapper, running at its own address, computes
gp = t9 + (_gp - __GI_<name>_fallback) != _gp and faults on its first
GOT access. The public symbol is fine (strong via libpthread_hidden_def);
only the __GI_ alias is weak, hence the asymmetry. (NPTL is unaffected:
there lt_libc_hidden is libc_hidden_def, i.e. a strong __GI_, and there is
no separate wrapsyscall wrapper.)

Add CANCELABLE_SYSCALL_STRONG -- identical to CANCELABLE_SYSCALL but
without the weak attribute -- and use it for the wrappers that also export
a __GI_ alias (waitpid, nanosleep). The strong __GI_ then wins the link
and is co-located with the wrapper, so %gp_rel(__GI_<name>) yields the
correct gp. The other wrappers (read, write, ...) export no __GI_ here, so
they are unaffected. Verified on mips64-be-n32: the testsuite SIGSEGVs are
gone (sha256c-test, fork, clone, tst-getcwd, tst-mallocfork, ... pass).

Signed-off-by: Ramin Moussavi <ramin.moussavi@yacoub.de>
ramin 4 дней назад
Родитель
Сommit
d0226d2b1e
1 измененных файлов с 22 добавлено и 2 удалено
  1. 22 2
      libpthread/linuxthreads/wrapsyscall.c

+ 22 - 2
libpthread/linuxthreads/wrapsyscall.c

@@ -54,6 +54,26 @@ name param_list									\
   return result;								\
 }
 
+/* Like CANCELABLE_SYSCALL but the wrapper is a strong definition.  Used for
+ * the functions that also export a __GI_ alias (libpthread_hidden_def): a weak
+ * wrapper makes that __GI_ symbol weak too, and on MIPS a function derives its
+ * PIC gp via %gp_rel(__GI_name) -- with two weak __GI_name (this wrapper and
+ * the libc fallback) the linker may pick the fallback, at a different address,
+ * so the wrapper computes a wrong gp and SIGSEGVs in its prologue.  A strong
+ * wrapper makes its __GI_ win the link, co-located with the code. */
+#define CANCELABLE_SYSCALL_STRONG(res_type, name, param_list, params)		\
+res_type __libc_##name param_list;						\
+res_type									\
+name param_list									\
+{										\
+  res_type result;								\
+  int oldtype;									\
+  pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype);		\
+  result = __libc_##name params;						\
+  pthread_setcanceltype (oldtype, NULL);					\
+  return result;								\
+}
+
 #define CANCELABLE_SYSCALL_VA(res_type, name, param_list, params, last_arg)	\
 res_type __libc_##name param_list;						\
 res_type									\
@@ -108,7 +128,7 @@ CANCELABLE_SYSCALL (int, msync, (void *addr, size_t length, int flags),
 
 /* nanosleep(2).  */
 libpthread_hidden_proto(nanosleep)
-CANCELABLE_SYSCALL (int, nanosleep, (const struct timespec *requested_time,
+CANCELABLE_SYSCALL_STRONG (int, nanosleep, (const struct timespec *requested_time,
 				     struct timespec *remaining),
 		    (requested_time, remaining))
 libpthread_hidden_def(nanosleep)
@@ -173,7 +193,7 @@ CANCELABLE_SYSCALL (__pid_t, wait, (__WAIT_STATUS_DEFN stat_loc), (stat_loc))
 
 /* waitpid(2).  */
 libpthread_hidden_proto(waitpid)
-CANCELABLE_SYSCALL (__pid_t, waitpid, (__pid_t pid, int *stat_loc,
+CANCELABLE_SYSCALL_STRONG (__pid_t, waitpid, (__pid_t pid, int *stat_loc,
 				       int options),
 		    (pid, stat_loc, options))
 libpthread_hidden_def(waitpid)