diff options
author | Denys Vlasenko <dvlasenk@redhat.com> | 2011-08-19 17:41:28 +0200 |
---|---|---|
committer | Denys Vlasenko <dvlasenk@redhat.com> | 2011-08-23 12:53:01 +0200 |
commit | d9560c108099394281012eb4bd7c46a46df6d31d (patch) | |
tree | da57c9bda67d8d48dcd6fcc71370c2f1ce19105c /signal.c | |
parent | 9aa97968ed75103c1f8a18e079b73328710ebe1e (diff) | |
download | strace-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.c | 46 |
1 files changed, 40 insertions, 6 deletions
@@ -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; } |