Re: lsattr: incorrect size for ioctl result

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 6/28/21 11:33 AM, Theodore Ts'o wrote:
> On Fri, Jun 25, 2021 at 04:01:27AM -0500, Rob Landley wrote:
>> > No. The above is a lie.
>> 
>> --- a/include/uapi/linux/fs.h
>> +++ b/include/uapi/linux/fs.h
>> @@ -203,8 +203,8 @@ struct fsxattr {
>> 
>>  #define        FS_IOC_GETFLAGS                 _IOR('f', 1, long)
>>  #define        FS_IOC_SETFLAGS                 _IOW('f', 2, long)
>> -#define        FS_IOC_GETVERSION               _IOR('v', 1, long)
>> -#define        FS_IOC_SETVERSION               _IOW('v', 2, long)
>> +#define        FS_IOC_GETVERSION               _IOR('v', 1, unsigned int)
>> +#define        FS_IOC_SETVERSION               _IOW('v', 2, unsigned int)
>>  #define FS_IOC_FIEMAP                  _IOWR('f', 11, struct fiemap)
>>  #define FS_IOC32_GETFLAGS              _IOR('f', 1, int)
>>  #define FS_IOC32_SETFLAGS              _IOW('f', 2, int)
> 
> The problem is that there are a large number of userspace programs
> which are using _IOR('v', 1, long) (the codepoint for
> FS_IOC_GETVERSION for decades), but are expecting the kernel to fill
> in an int.

Because if we don't any 64 bit version breaks on big endian. (And possibly also
if userspace didn't prezero the long.)

I agree changing the behavior of FS_IOC_* to do what the export says it does
would break binary compatibility, which is why my patch went the other way.
There's already FS_IOC32_* and I thought the suggested change would export their
ioctl numbers for both sets of names. (Does that not work?)

Maybe for compatibility/cleanup/pruning internal codepaths something like:

--- a/fs/ioctl.c
+++ b/fs/ioctl.c
@@ -1144,13 +1144,13 @@ COMPAT_SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned
int, cmd,
 #endif

        /*
-        * These access 32-bit values anyway so no further handling is
-        * necessary.
+        * These are defined "long" but access int values, so force to IOC32
         */
-       case FS_IOC32_GETFLAGS:
-       case FS_IOC32_SETFLAGS:
-               cmd = (cmd == FS_IOC32_GETFLAGS) ?
-                       FS_IOC_GETFLAGS : FS_IOC_SETFLAGS;
+       case FS_IOC_GETVERSION:
+       case FS_IOC_SETVERSION:
+       case FS_IOC_GETFLAGS:
+       case FS_IOC_SETFLAGS:
+               if (sizeof(long) == 8) cmd ^= 12 << _IOC_SIZESHIFT;
                fallthrough;
        /*
         * everything else in do_vfs_ioctl() takes either a compatible

... would mean all the filesystems only ever see IOC32 and if they want to add
64 at some point probably use a new number for it?

At the moment I'm just trying to go "can the next person not hit the same issue
I hit, which Denys presumably notified me about because HE hit it".

> We also need to be a bit careful when we make these sorts of changes
> of #defines, so we don't break kernel code like this: 

*shrug* I dunno how it should be properly fixed, just heads up that it's borked.

> long ext2_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
> {
> 	/* These are just misnamed, they actually get/put from/to user an int */

Of course given that comment I guess they already knew. The comment was added 18
years ago:

https://git.kernel.org/pub/scm/linux/kernel/git/mpe/linux-fullhistory.git/commit/?id=835a765ed1e1ef5814c9e8c6e1b64b63f8a335c9

And yet the uapi headers still don't have any comments next to the ioctl export
warning that the long is wrong:

-- a/include/uapi/linux/fs.h
+++ b/include/uapi/linux/fs.h
@@ -201,15 +201,19 @@ struct fsxattr {

 #define FSLABEL_MAX 256        /* Max chars for the interface; each fs may
differ */

+/* Warning: these actually read/write an unsigned int, not long */
 #define        FS_IOC_GETFLAGS                 _IOR('f', 1, long)
 #define        FS_IOC_SETFLAGS                 _IOW('f', 2, long)
 #define        FS_IOC_GETVERSION               _IOR('v', 1, long)
 #define        FS_IOC_SETVERSION               _IOW('v', 2, long)
-#define FS_IOC_FIEMAP                  _IOWR('f', 11, struct fiemap)
+
+/* Functionally equivalent to the FS_IOC_{GET,SET}{FLAGS,VERSION} above */
 #define FS_IOC32_GETFLAGS              _IOR('f', 1, int)
 #define FS_IOC32_SETFLAGS              _IOW('f', 2, int)
 #define FS_IOC32_GETVERSION            _IOR('v', 1, int)
 #define FS_IOC32_SETVERSION            _IOW('v', 2, int)
+
+#define FS_IOC_FIEMAP                  _IOWR('f', 11, struct fiemap)
 #define FS_IOC_FSGETXATTR              _IOR('X', 31, struct fsxattr)
 #define FS_IOC_FSSETXATTR              _IOW('X', 32, struct fsxattr)
 #define FS_IOC_GETFSLABEL              _IOR(0x94, 49, char[FSLABEL_MAX])

> (This is from 4.4's fs/ext2/ioct.c; the point is if we want to "fix"
> the definition of *_IOC_GETFLAGS because of a pearl clutching fit that
...
>> Which raises the question "why is there an IOC32 version of this when it was
>> never NOT 32 bit" and "does GETFLAGS have the same problem"? (Haven't looked...)
> 
> Probably because the people who added the IOC32 versions didn't
> understand this at the time?  :-)

Let me see if I understand:

1) The API the kernel exports is not what the kernel is doing.
2) Userspace can't reliably use the API the way it's currently exported.
3) Even other kernel devs "didn't understand" it.
4) Fixing it would involve scare quotes and result from a pearl clutching fit.

... no, I'm pretty sure I don't understand.

*shrug* I've cc'd Michael Kerrisk in hopes he can update the man pages. "man
ioctl_list" already documents these ioctls correctishly (modulo the
signed/unsigned part) but might benefit from some sort of "warning, do not trust
the kernel headers here, they are wrong" comment.

Rob



[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux