Dear Maintainer,
please consider the behaviour described later as a serious defect in
rpcbind code: it renders standards-compliant NFS v3 client with locking
support enabled unusable.
Linux NLM code is unaffected: it drops IP address information
received from rpcbind completely and uses only remote port number, but
FreeBSD uses both and stucks with 'remote lockd is not responding'.
Steps to reproduce:
-------------------
1) Install Debian host system with several interfaces configured.
2) Install, e.g, FreeBSD virtual machine attached to the network on
Debian host configured on interface which is NOT THE FIRST ONE in, e.g,
'ifconfig -a' output.
3) Share a filesystem on the host and mount it inside the VM.
4) Try to edit any file using system '/usr/bin/vi'.
Steps to analyse:
-----------------
Try to run the following script inside VM:
===
#!/usr/bin/env python3
import os, socket, struct, sys
def xs(s: str) -> bytes:
b = s.encode()
return struct.pack('>I', len(b)) + b + b'\0' * (-len(b) % 4)
def getaddr(srv: str, hint: str) -> str:
xid = int.from_bytes(os.urandom(4), 'big')
req = (struct.pack('>10I', xid, 0, 2, 100000, 4, 3, 0, 0, 0, 0)
+ struct.pack('>2I', 100021, 4) + xs('udp') + xs(hint) + xs(''))
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.settimeout(3)
s.sendto(req, (srv, 111))
r = s.recv(65535)
rxid, mtype, rstat, _, vlen = struct.unpack_from('>5I', r)
if (rxid, mtype, rstat) != (xid, 1, 0):
raise RuntimeError('bad reply header')
off = 20 + vlen + (-vlen % 4)
astat, slen = struct.unpack_from('>2I', r, off)
if astat != 0:
raise RuntimeError(f'accept_stat {astat}')
return r[off + 8:off + 8 + slen].decode()
srv, me = sys.argv[1], sys.argv[2]
for h in ('', f'{me}.0.0', f'{srv}.0.111'):
print(f'r_addr={h!r:24} -> {getaddr(srv, h)}')
===
... with the following arguments:
root@host:~ # python3 getaddr.py [IP address of the host] [IP address of the VM]
Suppose the host/VM have the following IP configuration:
hostif0=172.19.1.0/16
hostif1=172.29.1.0/16
vmif0=172.29.3.2/16
Script will output something similar to:
r_addr='' -> 172.19.1.0.209.217
r_addr='172.29.3.2.0.0' -> 172.19.1.0.209.217
r_addr='172.29.1.0.0.111' -> 172.29.1.0.209.217
(209 and 217 will be substituted by your values)
This is obviously wrong. Correct output should be:
r_addr='' -> 172.29.1.0.209.217
r_addr='172.29.3.2.0.0' -> 172.29.1.0.209.217
r_addr='172.29.1.0.0.111' -> 172.29.1.0.209.217
How to fix:
-----------
Apply the following patch:
===
--- a/src/util.c
+++ b/src/util.c
@@ -103,7 +103,7 @@
addrmerge(struct netbuf *caller, char *serv_uaddr, char *clnt_uaddr,
char *netid)
{
- struct ifaddrs *ifap, *ifp = NULL, *bestif, *exactif;
+ struct ifaddrs *ifap, *ifp = NULL, *bestif, *netif, *exactif;
struct netbuf *serv_nbp = NULL, *hint_nbp = NULL, tbuf;
struct sockaddr *caller_sa, *hint_sa, *ifsa, *ifmasksa, *serv_sa;
struct sockaddr_storage ss;
@@ -157,8 +157,9 @@
* network portion of its address is equal to that of the client.
* If so, we have found the interface that we want to use.
*/
- bestif = NULL; /* first interface UP with same network & family */
+ bestif = NULL; /* first usable interface UP with same family */
+ netif = NULL; /* first interface UP with same network & family */
exactif = NULL; /* the interface requested by the client */
u_int8_t maskAllAddrBits[16] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; /* 16 bytes for IPv6 */
for (ifap = ifp; ifap != NULL; ifap = ifap->ifa_next) {
@@ -178,8 +179,8 @@
if (!bitmaskcmp(&SA2SINADDR(ifsa),
&SA2SINADDR(hint_sa), &SA2SINADDR(ifmasksa),
sizeof(struct in_addr))) {
- if(!bestif) /* for compatibility with previous code */
- bestif = ifap;
+ if (netif == NULL) /* first network match wins */
+ netif = ifap;
/* Is this an exact match? */
if (!bitmaskcmp(&SA2SINADDR(ifsa),
&SA2SINADDR(hint_sa), maskAllAddrBits,
@@ -208,8 +209,8 @@
} else if (!bitmaskcmp(&SA2SIN6ADDR(ifsa),
&SA2SIN6ADDR(hint_sa), &SA2SIN6ADDR(ifmasksa),
sizeof(struct in6_addr))) {
- if(!bestif) /* for compatibility with previous code */
- bestif = ifap;
+ if (netif == NULL) /* first network match wins */
+ netif = ifap;
/* Is this an exact match? */
if (!bitmaskcmp(&SA2SIN6ADDR(ifsa),
&SA2SIN6ADDR(hint_sa), maskAllAddrBits,
@@ -235,6 +236,8 @@
bestif = ifap;
}
+ if (netif != NULL)
+ bestif = netif;
if (bestif == NULL)
goto freeit;
===
History information:
--------------------
The defect was from 2018 code change (matching changes).