« November 2009
SunMonTueWedThuFriSat
1
2
3
4
5
6
7
8
9
10
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
     
       
Today
XML

Neat blogs

Navigation

Editing

Powered by Roller Weblogger.

statcounter.com

clustrmaps.com

Locations of visitors to this page

technorati.com

20080118 Friday January 18, 2008
Checking a host entry - some code analysis

I hark back to the stuff that mountd does because I know I should be sharing code with it when I am done with the prototype. The problem facing me now is how do I parse (and verify) a network address and also do a comparison. Instead of stating what I think is happening, I'm going to walk down some code - look in usr/src/cmd/fs.d/nfs/mountd/mountd.c for the latest version of it.

First off, we know networks start with a '@', so let's look for that:

   1380 			/*
   1381 			 * If the list name begins with an at
   1382 			 * sign then do a network comparison.
   1383 			 */
   1384 			if (*gr == '@') {
   1385 				if (netmatch(nb, gr + 1))
   1386 					return (response);

Okay, before we head down netmatch(), what is the context?

   1312 /*
   1313  * Determine whether an access list grants rights to a particular host.
   1314  * We match on aliases of the hostname as well as on the canonical name.
   1315  * Names in the access list may be either hosts or netgroups;  they're
   1316  * not distinguished syntactically.  We check for hosts first because
   1317  * it's cheaper (just M*N strcmp()s), then try netgroups.
   1318  */
   1319 int
   1320 in_access_list(struct netbuf *nb, struct nd_hostservlist *clnames,
   1321     char *access_list)	/* N.B. we clobber this "input" parameter */
   1322 {
...
   1331 
   1332 	/*
   1333 	 * If no access list - then it's unrestricted
   1334 	 */
   1335 	if (access_list == NULL || *access_list == '\0')
   1336 		return (1);
   1337 
   1338 	nentries = 0;
   1339 
   1340 	for (gr = strtok_r(access_list, ":", &lasts);
   1341 		gr != NULL; gr = strtok_r(NULL, ":", &lasts)) {

So we are walking down a ':' list of access list entries trying to determine whether or not a given client is in that list. Okay, right away I want to know if any verification has already occurred on that list. We can find one caller of in_access_list() here:

   1552 /*
   1553  * Given an export and the clients hostname(s)
   1554  * determine the security flavors that this
   1555  * client is permitted to use.
   1556  *
   1557  * This routine is called only for "old" syntax, i.e.
   1558  * only one security flavor is allowed.  So we need
   1559  * to determine two things: the particular flavor,
   1560  * and whether the client is allowed to use this
   1561  * flavor, i.e. is in the access list.
   1562  *
   1563  * Note that if there is no access list, then the
   1564  * default is that access is granted.
   1565  */
   1566 static int
   1567 getclientsflavors_old(struct share *sh, struct netbuf *nb,
   1568     struct nd_hostservlist *clnames, int *flavors)
   1569 {
...
   1573 
   1574 	opts = strdup(sh->sh_opts);
...
   1581 	p = opts;
   1582 
   1583 	while (*p) {
   1584 
   1585 		switch (getsubopt(&p, optlist, &val)) {
... 
   1590 		case OPT_RO:
   1591 		case OPT_RW:
   1592 			defaultaccess = 0;
   1593 			if (in_access_list(nb, clnames, val))
   1594 				ok++;

All of the instances seem to work on making a local copy of struct share *sh's sh_opts. I'm going to claim that this is a performance issue. I may back that up later. What I'm guessing is that we keep a string copy to make it easy to dump out the share and we claim that doing mount checks is rare. Which it may be, but remember that any NFS request may result in a door call from the kernel to do such a check. See nfsauth_svc() in this file.

So back to the question about whether the entry is validated when loaded into sh_opts? We can see that this code loads an entry:

    729 static int
    730 mount_enoent_error(char *path, char *rpath, struct nd_hostservlist *clnames,
    731     struct netbuf *nb, int *flavor_list)
    732 {
    733 	char *checkpath, *dp;
    734 	struct share *sh = NULL;
...
    744 	/* CONSTCOND */
    745 	while (1) {
    746 		if (sh) {
    747 			sharefree(sh);
    748 			sh = NULL;
    749 		}
    750 		if ((sh = findentry(rpath)) == NULL &&
    751 			(sh = find_lofsentry(rpath, &lofs_tried)) == NULL) {

Which in turn leads us to:

   1074 struct share *
   1075 findentry(char *path)
   1076 {
   1077 	struct share *sh = NULL;
...
   1083 	check_sharetab();
   1084 
   1085 	(void) rw_rdlock(&sharetab_lock);
   1086 
   1087 	for (shp = share_list; shp; shp = shp->shl_next) {
   1088 		sh = shp->shl_sh;

And then:

   1986 void
   1987 check_sharetab()
   1988 {
...
   2030 	/*
   2031 	 * Note that since the sharetab is now in memory
   2032 	 * and a snapshot is taken, we no longer have to
   2033 	 * lock the file.
   2034 	 */
   2035 	f = fopen(SHARETAB, "r");
...
   2041 
   2042 	/*
   2043 	 * Once we are sure /etc/dfs/sharetab has been
   2044 	 * modified, flush netgroup cache entries.
   2045 	 */
   2046 	netgrp_cache_flush();
   2047 
   2048 	sh_free(share_list);			/* free old list */
   2049 	share_list = NULL;
   2050 
   2051 	while ((res = getshare(f, &sh)) > 0) {

So, this can cause the sharetab(4) to be loaded (I've elided out the part that stat's it and returns if no change). And we have another lead down into getshare(), we are getting close. This rountine is actually over in usr/src/cmd/fs.d/nfs/lib/sharetab.c :

     44 /*
     45  * Get an entry from the share table.
     46  * There should be at least 4 fields:
     47  *
     48  * 	pathname  resource  fstype  options  [ description ]
     49  *
     50  * A fifth field (description) is optional.
     51  *
     52  * Returns:
     53  *	> 1  valid entry
     54  *	= 0  end of file
     55  *	< 0  error
     56  */
     57 int
     58 getshare(FILE *fd, share_t **shp)
...
     91 	sh->sh_opts = (char *)strtok_r(NULL, w, &lasts);
     92 	if (sh->sh_opts == NULL)
     93 		return (-3);
...
     99 	return (1);
    100 }

So, no checking is done on the access list when it is loaded. By the way, I can see two "bugs" in the above code. On line 53, the comment says that "> 1" is returned if valid, yet we can see that '1' is returned. And on line 93, what does '-3' correspond to? I.e., it is a magic number.

Back to netmatch() and the comparison:

   1406 int
   1407 netmatch(struct netbuf *nb, char *name)
   1408 {
   1409 	uint_t claddr;
...
   1416 	/*
   1417 	 * Check if it's an IPv4 addr
   1418 	 */
   1419 	if (nb->len != sizeof (struct sockaddr_in))
   1420 		return (0);
   1421 
   1422 	(void) memcpy(&claddr,
   1423 		/* LINTED pointer alignment */
   1424 		&((struct sockaddr_in *)nb->buf)->sin_addr.s_addr,
   1425 		sizeof (struct in_addr));
   1426 	claddr = ntohl(claddr);

So the netbuf is really a IPv4 sockaddr and the code states we do not handle mountd (or NFS requests which need verification) with IPv6. We also convert it to an uint_t claddr. Note we do that conversion each time in here, so if we had a huge list of networks, we would redo this many times.

And we do the conversion of the address here:

   1432 	if (isdigit(*name)) {
   1433 		/*
   1434 		 * Convert a dotted IP address
   1435 		 * to an IP address. The conversion
   1436 		 * is not the same as that in inet_addr().
   1437 		 */
   1438 		p = name;
   1439 		addr = 0;
   1440 		for (i = 0; i < 4; i++) {
   1441 			addr |= atoi(p) << ((3-i) * 8);
   1442 			p = strchr(p, '.');
   1443 			if (p == NULL)
   1444 				break;
   1445 			p++;
   1446 		}

So there is no checking to see that the address is valid. And we can see that here:

# share -F nfs -o rw=256.1.2.-13 /foo
# share
-               /foo   sec=sys,rw=256.1.2.-13   ""

And a simple program shows this in action:

% cat a.c
#include 
#include 
#include 

int
main (int argc, char *argv[])
{
        char    *p = argv[1];
        uint_t  addr = 0;
        int     i;

        for (i = 0; i < 4; i++) {
                addr |= atoi(p) << ((3-i) * 8);
                p = strchr(p, '.');
                if (p == NULL)
                        break;
                p++;
        }

        printf("%s yields %u\n", argv[1], addr);

        return (0);
}
% ./a.out 127.1.1.1
127.1.1.1 yields 2130772225
% ./a.out 192.168.2.1
192.168.2.1 yields 3232236033
% ./a.out 192.168.2.2
192.168.2.2 yields 3232236034
% ./a.out 256.1.2.-13
256.1.2.-13 yields 4294967283
% ./a.out 192.168.1.255
192.168.1.255 yields 3232236031
% ./a.out 192.168.1.256
192.168.1.256 yields 3232235776
% ./a.out 192.168..256
192.168..256 yields 3232235776
% ./a.out 192.168.1.-1
192.168.1.-1 yields 4294967295
% ./a.out 192.168.1.happy
192.168.1.happy yields 3232235776
% ./a.out 192.168.1.2happy
192.168.1.2happy yields 3232235778
% ./a.out 192helpme.168.1.2
192helpme.168.1.2 yields 3232235778
% ./a.out 192.168.1.2
192.168.1.2 yields 3232235778

The assumptions made in netmatch() would be okay if the share was verified correctly in getshare(). Note that I am not advocating keeping the share options as a ':' separated string. I think that sh_opts is to flat and causes too much parsing to be duplicated.

Also, I've mentioned in the past that OpenSolaris does not support single machine addresses (see [Open]Solaris and sharing subnets and single machines). We can finally see the reason here in netmatch():

   1412 	uint_t addr, mask;
...
   1460 	/*
   1461 	 * If the mask is specified explicitly then
   1462 	 * use that value, e.g.
   1463 	 *
   1464 	 *    @109.104.56/28
   1465 	 *
   1466 	 * otherwise assume a mask from the zero octets
   1467 	 * in the least significant bits of the address, e.g.
   1468 	 *
   1469 	 *   @109.104  or  @109.104.0.0
   1470 	 */
   1471 	if (mp) {
   1472 		bits = atoi(mp);
   1473 		mask = bits ? ~0 << ((sizeof (struct in_addr) * NBBY) - bits)
   1474 			: 0;
   1475 		addr &= mask;
   1476 	} else {
   1477 		if ((addr & 0x00ffffff) == 0)
   1478 			mask = 0xff000000;
   1479 		else if ((addr & 0x0000ffff) == 0)
   1480 			mask = 0xffff0000;
   1481 		else if ((addr & 0x000000ff) == 0)
   1482 			mask = 0xffffff00;
   1483 	}

If we added this code after 1482:

   1483 		else
   1484 			mask = 0xffffffff;

then single host addresses would be handled correctly.

Okay, I happen to like the way that mountd() converts the network address and netmask to unsigned integers. I'm going to steal the code and modify it. I need the error checking to occur for me and I do not want to be parsing a string every time I do a check.


Originally posted on Kool Aid Served Daily
Copyright (C) 2008, Kool Aid Served Daily

Trackback URL: http://blogs.sun.com/tdh/entry/checking_a_host_entry_some
Comments:

Post a Comment:

Name:
E-Mail:
URL:

Your Comment:

HTML Syntax: NOT allowed