siginfo_t fracturing, especially for 64/32-bit compatibility mode

From: Joe Korty
Date: Fri Jan 02 2004 - 14:52:25 EST


siginfo_t processing is fragile when in 32 bit compatibility mode on
a 64 bit processor. The kernel does conversions between 32 and 64
bit versions of siginfo_t and to do this, it must must always know
which of the (unioned) fields of siginfo are actually being used. I
believe this is the original purpose of the si_code field -- the
value in it should directly or indirectly indicate, unambigiously,
which of the fields in siginfo_t hold useful values.

rt_sigqueueinfo(2) subverts this by reserving a range of si_code
values for users, and there is nothing about them to indicate to the
kernel which fields of siginfo_t are actually in use. This is not a
problem in native mode as rt_sigqueueinfo simply passes the siginfo_t
argument through from the sending to the receiving process without
looking at or caring about the individual field values. But as
compatibility mode must convert and pass individual argument values,
it must know what fields are being used in each any every call to
rt_sigqueueinfo from a compatibility-mode 32 bit process, and each
time a siginfo structure is delivered to a compatibility-mode 32 bit
process (whether generated by a rt_sigqueueinfo from a 64 or 32 bit
process, or internally by the kernel).

A partial solution is to grep all uses of si_code in the kernel and
in glibc and tailor the architecture-specific 64 <-> 32 bit siginfo
kernel transform routines to current use. But this is fragile as it
does not take into account future glibc growth nor other users of
rt_sigqueueinfo outside of glibc, such as applications invoking
rt_sigqueueinfo directly.

Worse, in 2.6.0 and glibc-2.3.2, there are conflicts in current
si_code value assignments which affect both compatibility and native
mode users. When an application receives one of these siginfo_t's,
it cannot in general determine why it got it or which fields in the
siginfo_t it should extract and act upon. And when in compatibility
mode, the kernel cannot always determine which fields need to be
converted and passed on.

The current conflicts are:

SI_TKILL (used by the 2.6 kernel) and SI_ASYNCNL (used by
glibc-2.3.2) both have the same value (-6). If an application is
both threaded and is also using async IO, then it when it gets a
siginfo with the SI_TKILL / SI_ASYNCNL value then cannot reliably
decide which type it is. This may be solvable by changing glibc to
define another value for SI_ASYNCNL as this appears to be used only
by glibc in some process that is talking to itself.

SI_ASYNCIO is used by the kernel USB drivers to return the field
si_addr. Glibc also uses SI_ASYNCIO, but this time the fields
si_pid, si_uid, and si_value are used. As si_addr is unioned with
si_pid and si_uid, it is not possible for both sets to be correctly
converted when in compatibility mode, and even if they could be
converted, it still is not possible for application to safely
determine, when they receive one of these siginfo_t's, which set of
fields to extract and use. As glibc's use of SI_ASYNCIO appears
entirely internal to itself, it may be best to fix this problem by
defining a new SI_ASYNCIO_GLIBC value in glibc and have glibc use
that instead of SI_ASYNCIO. Or, if the USB driver usage is new and
no applications or libraries are using it yet, a new si_code value
could instead be defined for the USB drivers.

The 32-bit compatibility mode issues can be prevented from getting
any worse by eliminating the existing 'user' si_code value range
(grandfathering in all sensible current uses), then creating a new
'user' si_code system in which the caller declares which data fields
in siginfo_t are in use. This would operate much like _IO, _IOR, etc
do for ioctls. A possible definition:

#define __SI_USERMASK 0xff000000
#define __SI_USERCODE 0x20000000

#define __SI_BITMASK 0x00ffff00
#define __SI_PID 0x00800000 /* si_pid */
#define __SI_UID 0x00400000 /* si_uid */
#define __SI_TID 0x00200000 /* si_tid */
#define __SI_OVRUN 0x00100000 /* si_overrun */
#define __SI_PRIV 0x00080000 /* si_sys_private */
#define __SI_INCR 0x00040000 /* si_overrun_incr */
#define __SI_STAT 0x00020000 /* si_status */
#define __SI_UTIME 0x00010000 /* si_utime */
#define __SI_STIME 0x00008000 /* si_stime */
#define __SI_INT 0x00004000 /* si_int */
#define __SI_PTR 0x00002000 /* si_ptr */
#define __SI_ADDR 0x00001000 /* si_addr */
#define __SI_TRAPNO 0x00000800 /* si_trapno */
#define __SI_BAND 0x00000400 /* si_band */
#define __SI_FD 0x00000200 /* si_fd */

#define __SI_CMDMASK 0x000000ff

For example, glibc and other users would define any new user si_code
values they need by doing something like:

#define SI_FASYNCXX (__SI_USERCODE | __SI_UID | __SI_PID | __SI_INT | 17)

To work well, si_code values which do not match these new user values
or the grandfathered old user values would cause rt_sigqueueinfo to
return EINVAL. Also, to work well, copy_siginfo_to_user should be
changed to remove the old blanket-copy when si_code < 0 to one that
copies just the specified fields (except for the grandfathered
values).

Regards,
Joe
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/