Going after an undocumented Local Privilege Escalation OS vulnerability

First thing’s first.. getting started with Solaris 8 mdb, a kernel debugging utility. a modular debugger.
The vulnerability is in NameFS, according to these articles:
Sun Bug Id# 6581308
https://blogs.oracle.com/sunsecurity/entry/sun_alert_237986_a_security
http://dl.packetstormsecurity.net/0808-advisories/sa31356.txt
http://www.securityfocus.com/bid/30513/discuss

Solaris 8:
Apply patch 114984-02 or later.

Good hint here:
http://www.rapid7.com/db/vulnerabilities/sunpatch-114984

6581308 namefs wants to participate in the VFS_HOLD/VFS_RELE/VFS_FREEVFS protocol

I did some digging and found this patch to the code:
https://hg.openindiana.org/upstream/illumos/illumos-gate/rev/be657a5515de

Although, OpenSolaris uses some different functions than 2.8, such as vfs_matchops(),
https://hg.openindiana.org/upstream/illumos/illumos-gate/file/be657a5515de/usr/src/uts/common/fs/vfs.c

But perhaps the nature of the vulnerability will shine through these differences. I am already starting to think it’s being able to do operations that the filesystem wasn’t meant to do.
from /usr/src/uts/common/sys/vfs.h:
(vfs.c is in /usr/src/uts/common/fs)

/*
 * Structure per mounted file system.  Each mounted file system has
 * an array of operations and an instance record.
 *
 * The file systems are kept on a singly linked list headed by "rootvfs" and
 * terminated by NULL.  File system implementations should not access this
 * list; it's intended for use only in the kernel's vfs layer.
 */
typedef struct vfs {
	struct vfs	*vfs_next;		/* next VFS in VFS list */
	struct vfsops	*vfs_op;		/* operations on VFS */
	struct vnode	*vfs_vnodecovered;	/* vnode mounted on */
	uint_t		vfs_flag;		/* flags */
	uint_t		vfs_bsize;		/* native block size */
	int		vfs_fstype;		/* file system type index */
	fsid_t		vfs_fsid;		/* file system id */
	caddr_t		vfs_data;		/* private data */
	dev_t		vfs_dev;		/* device of mounted VFS */
	ulong_t		vfs_bcount;		/* I/O count (accounting) */
	ushort_t	vfs_nsubmounts;		/* immediate sub-mount count */
	struct vfs	*vfs_list;		/* sync list pointer */
	struct vfs	*vfs_hash;		/* hash list pointer */
	ksema_t		vfs_reflock;		/* mount/unmount/sync lock */
	uint_t		vfs_count;		/* vfs reference count */
	mntopts_t	vfs_mntopts;		/* options mounted with */
	char		*vfs_resource;		/* mounted resource string */
	char 		*vfs_mntpt;		/* mount point string */
	time_t		vfs_mtime;		/* time we were mounted */
} vfs_t;

typedef struct vfsops {
	int	(*vfs_mount)(struct vfs *, struct vnode *, struct mounta *,
			struct cred *);
	int	(*vfs_unmount)(struct vfs *, int, struct cred *);
	int	(*vfs_root)(struct vfs *, struct vnode **);
	int	(*vfs_statvfs)(struct vfs *, struct statvfs64 *);
	int	(*vfs_sync)(struct vfs *, short, struct cred *);
	int	(*vfs_vget)(struct vfs *, struct vnode **, struct fid *);
	int	(*vfs_mountroot)(struct vfs *, enum whymountroot);
	int	(*vfs_swapvp)(struct vfs *, struct vnode **, char *);
	void	(*vfs_freevfs)(struct vfs *);
} vfsops_t;

[..]
/*
 * Filesystem type switch table.
 */
typedef struct vfssw {
	char		*vsw_name;	/* type name string */
	int		(*vsw_init)(struct vfssw *, int);
					/* init routine */
	struct vfsops	*vsw_vfsops;	/* filesystem operations vector */
	int		vsw_flag;	/* flags */
	mntopts_t	*vsw_optproto;	/* mount options table prototype */
} vfssw_t;

It looks like a bunch of function pointers!! I noticed one check in the patched OpenIndiana code is that they “match” the ops.. Perhaps somehow these function pointers can be overwritten..

That brings me to the namefs files:
/usr/src/uts/common/fs/namefs/namevfs.c

int
nameinit(struct vfssw *vswp, int fstype)
{
	int dev;

	namefstype = fstype;
	vswp->vsw_vfsops = &nmvfsops;
	if ((dev = getudev()) == (major_t)-1) {
		cmn_err(CE_WARN, "nameinit: can't get unique device");
		dev = 0;
	}
	mutex_init(&ntable_lock, NULL, MUTEX_DEFAULT, NULL);
	namedev = makedevice(dev, 0);
	bzero(nm_filevp_hash, sizeof (nm_filevp_hash));
	namevfs.vfs_op = &dummyvfsops;
	namevfs.vfs_vnodecovered = NULL;
	namevfs.vfs_bsize = 1024;
	namevfs.vfs_fstype = namefstype;
	vfs_make_fsid(&namevfs.vfs_fsid, namedev, namefstype);
	namevfs.vfs_dev = namedev;
	return (0);
}

 

/*
 * Define the vfs operations vector.
 */
struct vfsops nmvfsops = {
	nm_mount,
	nm_unmount,
	nm_root,
	nm_statvfs,
	nm_sync,
	fs_nosys,	/* vget */
	fs_nosys,	/* mountroot */
	fs_nosys,	/* swapvp */
	fs_freevfs
};

struct vfsops dummyvfsops = {
	fs_nosys,	/* mount */
	fs_nosys,	/* unmount */
	fs_nosys,	/* root */
	nm_statvfs,
	nm_sync,
	fs_nosys,	/* vget */
	fs_nosys,	/* mountroot */
	fs_nosys,	/* swapvp */
	fs_freevfs
};

usr/src/uts/common/sys/vnode.h

/*
 * All of the fields in the vnode are read-only once they are initialized
 * (created) except for:
 *	v_flag:		protected by v_lock
 *	v_count:	protected by v_lock
 *	v_pages:	protected by v_lock
 *	v_filocks:	protected by flock_lock in flock.c
 */
typedef struct vnode {
	kmutex_t	v_lock;			/* protects vnode fields */
	u_short		v_flag;			/* vnode flags (see below) */
	u_short		v_count;		/* reference count */
	struct vfs 	_FAR_ *v_vfsmountedhere;	/* ptr to vfs mounted here */
	struct vnodeops	_FAR_ *v_op;			/* vnode operations */
	struct vfs	_FAR_ *v_vfsp;		/* ptr to containing VFS */
	struct stdata	_FAR_ *v_stream;		/* associated stream */
	struct page	_FAR_ *v_pages;		/* vnode pages list */
	enum vtype	v_type;			/* vnode type */
	dev_t		v_rdev;			/* device (VCHR, VBLK) */
	caddr_t		v_data;			/* private data for fs */
	struct filock	_FAR_ *v_filocks;		/* ptr to filock list */
	kcondvar_t	v_cv;			/* synchronize locking */
} vnode_t;

RECAP
So, NameFS wasn’t participating in this “VFS_HOLD/VFS_RELE/VFS_FREEVFS” protocol. It looks like some kind of record keeping wasn’t in place, the V filesystem was never getting freed, and perhaps the function pointers in the vops fields were getting overwritten… HMMMM

root@life[pts/4][~] mdb -k
Loading modules: [ unix krtld genunix ip usba s1394 nfs ipc ptm random lofs cpc ]
> nmvfsops/48X
nmvfsops:
nmvfsops:       0               780982c8        0               7809868c
                0               78098800        0               78098830
                0               780988a0        0               1009a0e4
                0               1009a0e4        0               1009a0e4
                0               1009a0ec        0               1009a0e4
                0               1009a0e4        0               1009a0e4
                0               78098830        0               780988a0
                0               1009a0e4        0               1009a0e4
                0               1009a0e4        0               1009a0ec
                0               78041fd8        0               780988f0
                0               78041d70        0               0
                0               0               0               10438890
> 780982c8/i
nm_mount:
nm_mount:       save      %sp, -0x140, %sp
>

Is it really possible to somehow overwrite this table?? … or is the reference to the table simply changed??
I have no idea what I’m looking for..

Posted in C, NameFS, Sparc/Solaris

Leave a Reply

Your email address will not be published. Required fields are marked *

*

Skip to toolbar