[PATCH] set saner MAX_ARGS

Denys Vlasenko dvlasenk at redhat.com
Thu Aug 18 10:23:51 UTC 2011


I noticed that tcp->u_args[MAX_ARGS] array is way larger than
I'd expect: for all arches except HPPA it has 32 (!) elements.

I looked at the code and so far I spotted only one abuser of
this fact: sys_sigreturn. On several arches, it saves sigset_t
into tcp->u_args[1...N] on entry and prints it on exit, a-la

    memcpy(&tcp->u_arg[1], &sc.oldmask[0], sizeof(sigset_t))

The problem here is that in glibc sigset_t is insanely large:
128 bytes, and using sizeof(sigset_t) in memcpy will overrun
&tcp->u_args[1] even with MAX_ARGS == 32:
On 32 bits, sizeof(tcp->u_args) == 32*4 == 128 bytes!
We may already have a bug there!

I propose to change the code to save NSIG / 8 bytes only.
NSIG can't ever be > 256, and in practice is <= 129,
thus NSIG / 8 is <= 16 bytes == 4 32-bit words,
and MAX_ARGS == 5 should be enough for saving signal masks.

The proposed patch is below.

Alternative solution is to make sys_sigreturn print mask
on entry, not on exit. What is the reson it doesn't do that now?

Please review.
-- 
vda



diff -d -urpN strace.5/defs.h strace.6/defs.h
--- strace.5/defs.h	2011-08-18 11:57:30.512416447 +0200
+++ strace.6/defs.h	2011-08-18 11:46:56.349540479 +0200
@@ -64,7 +64,7 @@
 #define DEFAULT_ACOLUMN	40	/* default alignment column for results */
 #endif
 #ifndef MAX_ARGS
-# ifdef HPPA
+# if defined HPPA || defined X86_64 || defined I386
 #  define MAX_ARGS	6	/* maximum number of args to a syscall */
 # else
 /* Way too big. Switch your arch to saner size after you tested that it works */
diff -d -urpN strace.5/signal.c strace.6/signal.c
--- strace.5/signal.c	2011-08-18 11:57:57.596606238 +0200
+++ strace.6/signal.c	2011-08-18 11:54:41.032179956 +0200
@@ -262,6 +262,28 @@ static const struct xlat sigprocmaskcmds
 #endif
 #endif
 
+/* Note on the size of sigset_t:
+ *
+ * In glibc, sigset_t is an array with space for 1024 bits (!),
+ * even though all arches supported by Linux have only 64 signals
+ * except MIPS, which has 128. IOW, it is 128 bytes long.
+ *
+ * In-kernel sigset_t is sized correctly (it is either 64 or 128 bit long).
+ * However, some old syscall return only 32 lower bits (one word).
+ * Example: sys_sigpending vs sys_rt_sigpending.
+ *
+ * Be aware of this fact when you try to
+ *     memcpy(&tcp->u_arg[1], &something, sizeof(sigset_t))
+ * - sizeof(sigset_t) is much bigger than you think,
+ * it may overflow tcp->u_arg[] array, and it may try to copy more data
+ * than is really available in <something>.
+ * Similarly,
+ *     umoven(tcp, addr, sizeof(sigset_t), &sigset)
+ * may be a bad idea: it'll try to read much more data than needed
+ * to fetch a sigset_t.
+ * Use (NSIG / 8) as a size instead.
+ */
+
 const char *
 signame(int sig)
 {
@@ -310,11 +332,21 @@ static const char *
 sprintsigmask(const char *str, sigset_t *mask, int rt)
 /* set might include realtime sigs */
 {
+	/* Was [8 * sizeof(sigset_t) * 8], but
+	 * glibc sigset_t is huge (1024 bits = 128 *bytes*),
+	 * and we were ending up with 8k (!) buffer here.
+	 *
+	 * No Unix system can have sig > 255
+	 * (waitpid API won't be able to indicate death from one)
+	 * and sig 0 doesn't exist either.
+	 * Therefore max possible no of sigs is 255: 1..255
+	 */
+	static char outstr[8 * 255];
+
 	int i, nsigs;
 	int maxsigs;
 	const char *format;
 	char *s;
-	static char outstr[8 * sizeof(sigset_t) * 8];
 
 	strcpy(outstr, str);
 	s = outstr + strlen(outstr);
@@ -1134,7 +1166,7 @@ sys_sigreturn(struct tcb *tcp)
 		if (umove(tcp, usp + __SIGNAL_FRAMESIZE, &sc) < 0)
 			return 0;
 		tcp->u_arg[0] = 1;
-		memcpy(&tcp->u_arg[1], &sc.oldmask[0], sizeof(sigset_t));
+		memcpy(&tcp->u_arg[1], &sc.oldmask[0], NSIG / 8);
 	} else {
 		tcp->u_rval = tcp->u_error = 0;
 		if (tcp->u_arg[0] == 0)





More information about the Strace-devel mailing list