summaryrefslogtreecommitdiff
path: root/signal.c
diff options
context:
space:
mode:
authorDenys Vlasenko <dvlasenk@redhat.com>2011-08-19 17:41:28 +0200
committerDenys Vlasenko <dvlasenk@redhat.com>2011-08-23 12:53:01 +0200
commitd9560c108099394281012eb4bd7c46a46df6d31d (patch)
treeda57c9bda67d8d48dcd6fcc71370c2f1ce19105c /signal.c
parent9aa97968ed75103c1f8a18e079b73328710ebe1e (diff)
downloadstrace-d9560c108099394281012eb4bd7c46a46df6d31d.tar.gz
strace-d9560c108099394281012eb4bd7c46a46df6d31d.tar.bz2
strace-d9560c108099394281012eb4bd7c46a46df6d31d.tar.xz
Set saner MAX_ARGS (6 or 8) for X86_64 and I386
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! This commit changes 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 even MAX_ARGS == 5 should be enough for saving signal masks. * defs.h: Reduce MAX_ARGS for X86_64 and I386 from 32 to 8 for FreeBSD and to 6 for everyone else. Add comment about current state of needed MAX_ARGS. * signal.c: Add comment about size of sigset_t. (sprintsigmask): Reduce static string buffer from 8k to 2k. (sys_sigreturn): Fix sigset saving to save only NSIG / 8 bytes, not sizeof(sigset_t) bytes. * linux/mips/syscallent.h: Reduce nargs of printargs-type syscall to 7. * linux/arm/syscallent.h: Reduce nargs of printargs-type syscall to 6. * linux/i386/syscallent.h: Likewise. * linux/m68k/syscallent.h: Likewise. * linux/powerpc/syscallent.h: Likewise. * linux/s390/syscallent.h: Likewise. * linux/s390x/syscallent.h: Likewise. * linux/sh/syscallent.h: Likewise. * linux/sh64/syscallent.h: Likewise. * linux/sparc/syscallent.h: Likewise. Signed-off-by: Denys Vlasenko <dvlasenk@redhat.com>
Diffstat (limited to 'signal.c')
-rw-r--r--signal.c46
1 files changed, 40 insertions, 6 deletions
diff --git a/signal.c b/signal.c
index 889134a..8c9433d 100644
--- a/signal.c
+++ b/signal.c
@@ -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)
@@ -1177,14 +1209,15 @@ sys_sigreturn(struct tcb *tcp)
if (umove(tcp, sp + 16 + SIGFRAME_SC_OFFSET, &sc) < 0)
return 0;
tcp->u_arg[0] = 1;
- memcpy(tcp->u_arg + 1, &sc.sc_mask, sizeof(sc.sc_mask));
+ memcpy(tcp->u_arg + 1, &sc.sc_mask, NSIG / 8);
}
else {
sigset_t sigm;
tcp->u_rval = tcp->u_error = 0;
if (tcp->u_arg[0] == 0)
return 0;
- memcpy(&sigm, tcp->u_arg + 1, sizeof(sigm));
+ sigemptyset(&sigm);
+ memcpy(&sigm, tcp->u_arg + 1, NSIG / 8);
tcp->auxstr = sprintsigmask("mask now ", &sigm, 0);
return RVAL_NONE | RVAL_STR;
}
@@ -1377,14 +1410,15 @@ sys_sigreturn(struct tcb *tcp)
if (umove(tcp, sp + SIGFRAME_UC_OFFSET, &uc) < 0)
return 0;
tcp->u_arg[0] = 1;
- memcpy(tcp->u_arg + 1, &uc.uc_sigmask, sizeof(uc.uc_sigmask));
+ memcpy(tcp->u_arg + 1, &uc.uc_sigmask, NSIG / 8);
}
else {
sigset_t sigm;
tcp->u_rval = tcp->u_error = 0;
if (tcp->u_arg[0] == 0)
return 0;
- memcpy(&sigm, tcp->u_arg + 1, sizeof(sigm));
+ sigemptyset(&sigm);
+ memcpy(&sigm, tcp->u_arg + 1, NSIG / 8);
tcp->auxstr = sprintsigmask("mask now ", &sigm, 0);
return RVAL_NONE | RVAL_STR;
}