Skip to content
  • Sergey Marinkevich's avatar
    netfilter: nft_exthdr: fix endianness of tcp option cast · 2e34328b
    Sergey Marinkevich authored
    
    
    I got a problem on MIPS with Big-Endian is turned on: every time when
    NF trying to change TCP MSS it returns because of new.v16 was greater
    than old.v16. But real MSS was 1460 and my rule was like this:
    
    	add rule table chain tcp option maxseg size set 1400
    
    And 1400 is lesser that 1460, not greater.
    
    Later I founded that main causer is cast from u32 to __be16.
    
    Debugging:
    
    In example MSS = 1400(HEX: 0x578). Here is representation of each byte
    like it is in memory by addresses from left to right(e.g. [0x0 0x1 0x2
    0x3]). LE — Little-Endian system, BE — Big-Endian, left column is type.
    
    	     LE               BE
    	u32: [78 05 00 00]    [00 00 05 78]
    
    As you can see, u32 representation will be casted to u16 from different
    half of 4-byte address range. But actually nf_tables uses registers and
    store data of various size. Actually TCP MSS stored in 2 bytes. But
    registers are still u32 in definition:
    
    	struct nft_regs {
    		union {
    			u32			data[20];
    			struct nft_verdict	verdict;
    		};
    	};
    
    So, access like regs->data[priv->sreg] exactly u32. So, according to
    table presents above, per-byte representation of stored TCP MSS in
    register will be:
    
    	                     LE               BE
    	(u32)regs->data[]:   [78 05 00 00]    [05 78 00 00]
    	                                       ^^ ^^
    
    We see that register uses just half of u32 and other 2 bytes may be
    used for some another data. But in nft_exthdr_tcp_set_eval() it casted
    just like u32 -> __be16:
    
    	new.v16 = src
    
    But u32 overfill __be16, so it get 2 low bytes. For clarity draw
    one more table(<xx xx> means that bytes will be used for cast).
    
    	                     LE                 BE
    	u32:                 [<78 05> 00 00]    [00 00 <05 78>]
    	(u32)regs->data[]:   [<78 05> 00 00]    [05 78 <00 00>]
    
    As you can see, for Little-Endian nothing changes, but for Big-endian we
    take the wrong half. In my case there is some other data instead of
    zeros, so new MSS was wrongly greater.
    
    For shooting this bug I used solution for ports ranges. Applying of this
    patch does not affect Little-Endian systems.
    
    Signed-off-by: default avatarSergey Marinkevich <sergey.marinkevich@eltex-co.ru>
    Acked-by: default avatarFlorian Westphal <fw@strlen.de>
    Signed-off-by: default avatarPablo Neira Ayuso <pablo@netfilter.org>
    2e34328b