This code resides in fs/ecryptfs/inode.c and the above snippet was taken from 2.6.31 release of the Linux kernel. As you can see, it retrieves the lower dentry using ecryptfs_dentry_to_lower() and subsequently it locks the parent using lock_parent() and it then calls the VFS layer routine vfs_unlink() to perform the actual unlinking.
The latter function can be found at fs/namei.c and among others it will execute this:

So, unless the dentry’s flags include DCACHE_NFSFS_RENAMED which as we can read from include/linux/dcache.h is simply:

#define DCACHE_NFSFS_RENAMED 0x0002 /* this dentry has been "silly
* renamed" and has to be
* deleted on the last dput()
*/

it will call fsnotify_link_count() from include/linux/fsnotify.h to issue a notify that the inode’s link count described by an atomic_t variable named ‘d_count’ is changed. Finally, d_delete() from fs/dcache.c is invoked and this will execute this code:

The final call to fsnotify_nameremove() will just issue another notification that the filename was removed from the directory. The problem with the above call to vfs_unlink() from ecryptfs_unlink() was that there was no locking involved. This means that even though the VFS layer would have decremented the ‘d_count’ and set the ‘d_inode’ to NULL, a user could use some read() or write() to access that inode leading to a NULL pointer dereference. For example, here is some code from ecryptfs_read_update_atime() routine:

/**
* ecryptfs_read_update_atime
*
* generic_file_read updates the atime of upper layer inode. But, it
* doesn't give us a chance to update the atime of the lower layer
* inode. This function is a wrapper to generic_file_read. It
* updates the atime of the lower level inode if generic_file_read
* returns without any errors. This is to be used only for file reads.
* The function to be used for directory reads is ecryptfs_read.
*/
static ssize_t ecryptfs_read_update_atime(struct kiocb *iocb,
const struct iovec *iov,
unsigned long nr_segs, loff_t pos)
{
int rc;
struct dentry *lower_dentry;
...
lower_dentry = ecryptfs_dentry_to_lower(file->f_path.dentry);
lower_vfsmount = ecryptfs_dentry_to_lower_mnt(file->f_path.dentry);
touch_atime(lower_vfsmount, lower_dentry);
...
}

Here, it retrieves the lower dentry and then uses it in touch_atime(). If we move to that routine we’ll see what will happen in case of NULL ‘d_inode’ because of the previous unchecked call to vfs_unlink()…

So, multiple NULL pointer dereferences arise since ‘dentry->d_inode’ could be NULL. Similar scenario also appears when calling ecryptfs_getxattr() which as we can read from fs/ecryptfs/inode.c is just a wrapper around ecryptfs_getxattr_lower() like this:

Here we can find something really handy regarding the exploitation of that vulnerability… Since ecryptfs_getxattr() will access the lower dentry, meaning the one that might contain a NULL ‘d_inode’, it would result in some interesting behavior in case of this code path…

So, if ‘lower_dentry->d_inode->i_op->getxattr’ function pointer is not NULL, which it would definitely not going to be, it will lock the “NULL->i_mutex” lock and then call the callback routine located at “NULL->i_op->getxattr” leading to quite simple code execution if you have mapped some function pointer there.
Anyway, this was patched by updating the ecryptfs_unlink() to use dget() before retrieving the lower dentry like this:

His aim is to utilize the codepath I described through getxattr() callback. This means that he’ll have to create a structure similar to the one he gave in the initial comments in the first page of the virtual address space. Now, to the actual code…

As you can see, he uses “pa__init()” which means that you can use it as a pulseaudio library to bypass the MMAP_MIN_ADDR restriction. However, he also provides the ability to execute it as a standalone application directly from main() routine. In either case, runexploit() is executed, so let’s have a look at it…

He retrieves the current personality and if it equals the SystemV Release 4 one (the pulseaudio bypass trick), it will change the NULL page’s permissions to readable/writable/executable using mprotect(2). Otherwise, he uses mmap(2) to attempt to map it. It is important to note here that he always uses ‘0x1000’ as the page size which it isn’t really portable in my opinion. Dynamically retrieving the page size through getpagesize(2) would be much better.
After getting the NULL page, variables ‘uid’ and ‘gid’ are updated with the current ones and malicious structure is constructed using the direct values ‘INODE_IOP_OFF’, ‘INODE_MUTEX_OFF’ and ‘INODEOPS_GETXATTR_OFF’. This is also a bad practise since those offsets might not work under 64-bit systems. A better solution would be to create a dummy structure from the original dentry structure from include/linux/dcache.h and use this to calculate the offsets dynamically on compile time.
After creating the malicious structure, he initializes his mutex structure (in which by the way, he is doing a more dynamic calculation of the offsets) by setting its address to 0x10, its owner to the address 0x20 and its locks appropriately.
At last, since the NULL is ready to trick the kernel into believing that it contains a valid dentry, it calls trigger()…

This code is similar to the trigger strace output provided by Tyler Hicks. He creates two files using open(2), then uses link(2) to create a hardlink from ‘buf1’ to ‘buf2’ to keep the inode that will be dereferenced. Unlink the ‘buf1’ to cause the vfs_unlink() call in ecryptfs_unlink() and then attempt to open(2) the ‘buf2’ hardlink that will have a ‘dentry->d_inode’ pointing to NULL. Finally, to force the kernel into accessing that inode he uses a dummy write(2) call.
For completeness, here are the rest routines from ‘paokara.c’ exploit code…

He uses get_current() to get the current ‘task_struct’ and then he iterates in the structure to find the credentials and update them to that of root. He also included code for newer credential records. Back to runexploit() the last system call to be executed will spawn a shell, hopefully with the privileges of the overwritten credential record.