@ -44,6 +44,10 @@ struct inet_diag_entry {
u16 dport ;
u16 family ;
u16 userlocks ;
# if IS_ENABLED(CONFIG_IPV6)
struct in6_addr saddr_storage ; /* for IPv4-mapped-IPv6 addresses */
struct in6_addr daddr_storage ; /* for IPv4-mapped-IPv6 addresses */
# endif
} ;
static DEFINE_MUTEX ( inet_diag_table_mutex ) ;
@ -428,25 +432,31 @@ static int inet_diag_bc_run(const struct nlattr *_bc,
break ;
}
if ( cond - > prefix_len = = 0 )
break ;
if ( op - > code = = INET_DIAG_BC_S_COND )
addr = entry - > saddr ;
else
addr = entry - > daddr ;
if ( cond - > family ! = AF_UNSPEC & &
cond - > family ! = entry - > family ) {
if ( entry - > family = = AF_INET6 & &
cond - > family = = AF_INET ) {
if ( addr [ 0 ] = = 0 & & addr [ 1 ] = = 0 & &
addr [ 2 ] = = htonl ( 0xffff ) & &
bitstring_match ( addr + 3 ,
cond - > addr ,
cond - > prefix_len ) )
break ;
}
yes = 0 ;
break ;
}
if ( cond - > prefix_len = = 0 )
break ;
if ( bitstring_match ( addr , cond - > addr ,
cond - > prefix_len ) )
break ;
if ( entry - > family = = AF_INET6 & &
cond - > family = = AF_INET ) {
if ( addr [ 0 ] = = 0 & & addr [ 1 ] = = 0 & &
addr [ 2 ] = = htonl ( 0xffff ) & &
bitstring_match ( addr + 3 , cond - > addr ,
cond - > prefix_len ) )
break ;
}
yes = 0 ;
break ;
}
@ -509,6 +519,55 @@ static int valid_cc(const void *bc, int len, int cc)
return 0 ;
}
/* Validate an inet_diag_hostcond. */
static bool valid_hostcond ( const struct inet_diag_bc_op * op , int len ,
int * min_len )
{
int addr_len ;
struct inet_diag_hostcond * cond ;
/* Check hostcond space. */
* min_len + = sizeof ( struct inet_diag_hostcond ) ;
if ( len < * min_len )
return false ;
cond = ( struct inet_diag_hostcond * ) ( op + 1 ) ;
/* Check address family and address length. */
switch ( cond - > family ) {
case AF_UNSPEC :
addr_len = 0 ;
break ;
case AF_INET :
addr_len = sizeof ( struct in_addr ) ;
break ;
case AF_INET6 :
addr_len = sizeof ( struct in6_addr ) ;
break ;
default :
return false ;
}
* min_len + = addr_len ;
if ( len < * min_len )
return false ;
/* Check prefix length (in bits) vs address length (in bytes). */
if ( cond - > prefix_len > 8 * addr_len )
return false ;
return true ;
}
/* Validate a port comparison operator. */
static inline bool valid_port_comparison ( const struct inet_diag_bc_op * op ,
int len , int * min_len )
{
/* Port comparisons put the port in a follow-on inet_diag_bc_op. */
* min_len + = sizeof ( struct inet_diag_bc_op ) ;
if ( len < * min_len )
return false ;
return true ;
}
static int inet_diag_bc_audit ( const void * bytecode , int bytecode_len )
{
const void * bc = bytecode ;
@ -516,29 +575,39 @@ static int inet_diag_bc_audit(const void *bytecode, int bytecode_len)
while ( len > 0 ) {
const struct inet_diag_bc_op * op = bc ;
int min_len = sizeof ( struct inet_diag_bc_op ) ;
//printk("BC: %d %d %d {%d} / %d\n", op->code, op->yes, op->no, op[1].no, len);
switch ( op - > code ) {
case INET_DIAG_BC_AUTO :
case INET_DIAG_BC_S_COND :
case INET_DIAG_BC_D_COND :
if ( ! valid_hostcond ( bc , len , & min_len ) )
return - EINVAL ;
break ;
case INET_DIAG_BC_S_GE :
case INET_DIAG_BC_S_LE :
case INET_DIAG_BC_D_GE :
case INET_DIAG_BC_D_LE :
case INET_DIAG_BC_JMP :
if ( op - > no < 4 | | op - > no > len + 4 | | op - > no & 3 )
return - EINVAL ;
if ( op - > no < len & &
! valid_cc ( bytecode , bytecode_len , len - op - > no ) )
if ( ! valid_port_comparison ( bc , len , & min_len ) )
return - EINVAL ;
break ;
case INET_DIAG_BC_AUTO :
case INET_DIAG_BC_JMP :
case INET_DIAG_BC_NOP :
break ;
default :
return - EINVAL ;
}
if ( op - > yes < 4 | | op - > yes > len + 4 | | op - > yes & 3 )
if ( op - > code ! = INET_DIAG_BC_NOP ) {
if ( op - > no < min_len | | op - > no > len + 4 | | op - > no & 3 )
return - EINVAL ;
if ( op - > no < len & &
! valid_cc ( bytecode , bytecode_len , len - op - > no ) )
return - EINVAL ;
}
if ( op - > yes < min_len | | op - > yes > len + 4 | | op - > yes & 3 )
return - EINVAL ;
bc + = op - > yes ;
len - = op - > yes ;
@ -596,6 +665,36 @@ static int inet_twsk_diag_dump(struct inet_timewait_sock *tw,
cb - > nlh - > nlmsg_seq , NLM_F_MULTI , cb - > nlh ) ;
}
/* Get the IPv4, IPv6, or IPv4-mapped-IPv6 local and remote addresses
* from a request_sock . For IPv4 - mapped - IPv6 we must map IPv4 to IPv6 .
*/
static inline void inet_diag_req_addrs ( const struct sock * sk ,
const struct request_sock * req ,
struct inet_diag_entry * entry )
{
struct inet_request_sock * ireq = inet_rsk ( req ) ;
# if IS_ENABLED(CONFIG_IPV6)
if ( sk - > sk_family = = AF_INET6 ) {
if ( req - > rsk_ops - > family = = AF_INET6 ) {
entry - > saddr = inet6_rsk ( req ) - > loc_addr . s6_addr32 ;
entry - > daddr = inet6_rsk ( req ) - > rmt_addr . s6_addr32 ;
} else if ( req - > rsk_ops - > family = = AF_INET ) {
ipv6_addr_set_v4mapped ( ireq - > loc_addr ,
& entry - > saddr_storage ) ;
ipv6_addr_set_v4mapped ( ireq - > rmt_addr ,
& entry - > daddr_storage ) ;
entry - > saddr = entry - > saddr_storage . s6_addr32 ;
entry - > daddr = entry - > daddr_storage . s6_addr32 ;
}
} else
# endif
{
entry - > saddr = & ireq - > loc_addr ;
entry - > daddr = & ireq - > rmt_addr ;
}
}
static int inet_diag_fill_req ( struct sk_buff * skb , struct sock * sk ,
struct request_sock * req ,
struct user_namespace * user_ns ,
@ -637,8 +736,10 @@ static int inet_diag_fill_req(struct sk_buff *skb, struct sock *sk,
r - > idiag_inode = 0 ;
# if IS_ENABLED(CONFIG_IPV6)
if ( r - > idiag_family = = AF_INET6 ) {
* ( struct in6_addr * ) r - > id . idiag_src = inet6_rsk ( req ) - > loc_addr ;
* ( struct in6_addr * ) r - > id . idiag_dst = inet6_rsk ( req ) - > rmt_addr ;
struct inet_diag_entry entry ;
inet_diag_req_addrs ( sk , req , & entry ) ;
memcpy ( r - > id . idiag_src , entry . saddr , sizeof ( struct in6_addr ) ) ;
memcpy ( r - > id . idiag_dst , entry . daddr , sizeof ( struct in6_addr ) ) ;
}
# endif
@ -691,18 +792,7 @@ static int inet_diag_dump_reqs(struct sk_buff *skb, struct sock *sk,
continue ;
if ( bc ) {
entry . saddr =
# if IS_ENABLED(CONFIG_IPV6)
( entry . family = = AF_INET6 ) ?
inet6_rsk ( req ) - > loc_addr . s6_addr32 :
# endif
& ireq - > loc_addr ;
entry . daddr =
# if IS_ENABLED(CONFIG_IPV6)
( entry . family = = AF_INET6 ) ?
inet6_rsk ( req ) - > rmt_addr . s6_addr32 :
# endif
& ireq - > rmt_addr ;
inet_diag_req_addrs ( sk , req , & entry ) ;
entry . dport = ntohs ( ireq - > rmt_port ) ;
if ( ! inet_diag_bc_run ( bc , & entry ) )