| From a32744d4abae24572eff7269bc17895c41bd0085 Mon Sep 17 00:00:00 2001 |
| From: Ian Kent <raven@themaw.net> |
| Date: Wed, 22 Feb 2012 20:45:44 +0800 |
| Subject: autofs: work around unhappy compat problem on x86-64 |
| |
| From: Ian Kent <raven@themaw.net> |
| |
| commit a32744d4abae24572eff7269bc17895c41bd0085 upstream. |
| |
| When the autofs protocol version 5 packet type was added in commit |
| 5c0a32fc2cd0 ("autofs4: add new packet type for v5 communications"), it |
| obvously tried quite hard to be word-size agnostic, and uses explicitly |
| sized fields that are all correctly aligned. |
| |
| However, with the final "char name[NAME_MAX+1]" array at the end, the |
| actual size of the structure ends up being not very well defined: |
| because the struct isn't marked 'packed', doing a "sizeof()" on it will |
| align the size of the struct up to the biggest alignment of the members |
| it has. |
| |
| And despite all the members being the same, the alignment of them is |
| different: a "__u64" has 4-byte alignment on x86-32, but native 8-byte |
| alignment on x86-64. And while 'NAME_MAX+1' ends up being a nice round |
| number (256), the name[] array starts out a 4-byte aligned. |
| |
| End result: the "packed" size of the structure is 300 bytes: 4-byte, but |
| not 8-byte aligned. |
| |
| As a result, despite all the fields being in the same place on all |
| architectures, sizeof() will round up that size to 304 bytes on |
| architectures that have 8-byte alignment for u64. |
| |
| Note that this is *not* a problem for 32-bit compat mode on POWER, since |
| there __u64 is 8-byte aligned even in 32-bit mode. But on x86, 32-bit |
| and 64-bit alignment is different for 64-bit entities, and as a result |
| the structure that has exactly the same layout has different sizes. |
| |
| So on x86-64, but no other architecture, we will just subtract 4 from |
| the size of the structure when running in a compat task. That way we |
| will write the properly sized packet that user mode expects. |
| |
| Not pretty. Sadly, this very subtle, and unnecessary, size difference |
| has been encoded in user space that wants to read packets of *exactly* |
| the right size, and will refuse to touch anything else. |
| |
| Reported-and-tested-by: Thomas Meyer <thomas@m3y3r.de> |
| Signed-off-by: Ian Kent <raven@themaw.net> |
| Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> |
| Cc: Jonathan Nieder <jrnieder@gmail.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| fs/autofs4/autofs_i.h | 1 + |
| fs/autofs4/dev-ioctl.c | 1 + |
| fs/autofs4/inode.c | 2 ++ |
| fs/autofs4/waitq.c | 22 +++++++++++++++++++--- |
| 4 files changed, 23 insertions(+), 3 deletions(-) |
| |
| --- a/fs/autofs4/autofs_i.h |
| +++ b/fs/autofs4/autofs_i.h |
| @@ -125,6 +125,7 @@ struct autofs_sb_info { |
| int sub_version; |
| int min_proto; |
| int max_proto; |
| + int compat_daemon; |
| unsigned long exp_timeout; |
| unsigned int type; |
| int reghost_enabled; |
| --- a/fs/autofs4/dev-ioctl.c |
| +++ b/fs/autofs4/dev-ioctl.c |
| @@ -389,6 +389,7 @@ static int autofs_dev_ioctl_setpipefd(st |
| sbi->pipefd = pipefd; |
| sbi->pipe = pipe; |
| sbi->catatonic = 0; |
| + sbi->compat_daemon = is_compat_task(); |
| } |
| out: |
| mutex_unlock(&sbi->wq_mutex); |
| --- a/fs/autofs4/inode.c |
| +++ b/fs/autofs4/inode.c |
| @@ -19,6 +19,7 @@ |
| #include <linux/parser.h> |
| #include <linux/bitops.h> |
| #include <linux/magic.h> |
| +#include <linux/compat.h> |
| #include "autofs_i.h" |
| #include <linux/module.h> |
| |
| @@ -341,6 +342,7 @@ int autofs4_fill_super(struct super_bloc |
| set_autofs_type_indirect(&sbi->type); |
| sbi->min_proto = 0; |
| sbi->max_proto = 0; |
| + sbi->compat_daemon = is_compat_task(); |
| mutex_init(&sbi->wq_mutex); |
| spin_lock_init(&sbi->fs_lock); |
| sbi->queues = NULL; |
| --- a/fs/autofs4/waitq.c |
| +++ b/fs/autofs4/waitq.c |
| @@ -90,7 +90,24 @@ static int autofs4_write(struct file *fi |
| |
| return (bytes > 0); |
| } |
| - |
| + |
| +/* |
| + * The autofs_v5 packet was misdesigned. |
| + * |
| + * The packets are identical on x86-32 and x86-64, but have different |
| + * alignment. Which means that 'sizeof()' will give different results. |
| + * Fix it up for the case of running 32-bit user mode on a 64-bit kernel. |
| + */ |
| +static noinline size_t autofs_v5_packet_size(struct autofs_sb_info *sbi) |
| +{ |
| + size_t pktsz = sizeof(struct autofs_v5_packet); |
| +#if defined(CONFIG_X86_64) && defined(CONFIG_COMPAT) |
| + if (sbi->compat_daemon > 0) |
| + pktsz -= 4; |
| +#endif |
| + return pktsz; |
| +} |
| + |
| static void autofs4_notify_daemon(struct autofs_sb_info *sbi, |
| struct autofs_wait_queue *wq, |
| int type) |
| @@ -147,8 +164,7 @@ static void autofs4_notify_daemon(struct |
| { |
| struct autofs_v5_packet *packet = &pkt.v5_pkt.v5_packet; |
| |
| - pktsz = sizeof(*packet); |
| - |
| + pktsz = autofs_v5_packet_size(sbi); |
| packet->wait_queue_token = wq->wait_queue_token; |
| packet->len = wq->name.len; |
| memcpy(packet->name, wq->name.name, wq->name.len); |