xorl %eax, %eax

CVE-2009-0787: Linux kernel eCryptFS Information Leak

leave a comment »

Information leak vulnerabilities can sometimes be extremely useful and this one leaks up to 4KB of kernel memory which is definitely enough to contain sensitive information. So, it was reported by Florian Streibelt and later fixed by Tyler Hicks on 20 March 2009 at 2.6.28.9 according to its changelog. The following code was taken from 2.6.28.8 release of the Linux kernel. Here is the vulnerable function from fs/ecryptfs/crypto.c:

1340 /**
1341  * ecryptfs_write_metadata
1342  * @ecryptfs_dentry: The eCryptfs dentry
1343  *
1344  * Write the file headers out.  This will likely involve a userspace
1345  * callout, in which the session key is encrypted with one or more
1346  * public keys and/or the passphrase necessary to do the encryption is
1347  * retrieved via a prompt.  Exactly what happens at this point should
1348  * be policy-dependent.
1349  *
1350  * Returns zero on success; non-zero on error
1351  */
1352 int ecryptfs_write_metadata(struct dentry *ecryptfs_dentry)
1353 {
1354        struct ecryptfs_crypt_stat *crypt_stat =
1355                &ecryptfs_inode_to_private(ecryptfs_dentry->d_inode)->crypt_stat;
1356        char *virt;
1357        size_t size = 0;
1358        int rc = 0;
         ...
1373        virt = (char *)get_zeroed_page(GFP_KERNEL);
1374        if (!virt) {
         ...
1379        rc = ecryptfs_write_headers_virt(virt, PAGE_CACHE_SIZE, &size,
1380                                         crypt_stat, ecryptfs_dentry);
1381        if (unlikely(rc)) {
         ...
1386        if (crypt_stat->flags & ECRYPTFS_METADATA_IN_XATTR)
1387                rc = ecryptfs_write_metadata_to_xattr(ecryptfs_dentry,
1388                                                      crypt_stat, virt, size);
1389        else
1390                rc = ecryptfs_write_metadata_to_contents(crypt_stat,
1391                                                         ecryptfs_dentry, virt);
1392        if (rc) {
         ...
1400        return rc;
1401 }

This isn’t anything complicated. It justs allocates a zeroed page using get_zeroed_page() at line 1373 and later calling ecryptfs_write_headers_virt() to write the headers at the virtual address pointed by virt of size defined by the second argument, PAGE_CACHE_SIZE (which is equal to PAGE_SIZE, for example on x86 is 4096, have a look at arch/x86/include/asm/page.h). Now, by having a really quick look at fs/ecryptfs/ecryptfs_kernel.h we can notice the following:

75 #define ECRYPTFS_MINIMUM_HEADER_EXTENT_SIZE 8192
         ...
216 /**
217  * This is the primary struct associated with each encrypted file.
218  *
219  * TODO: cache align/pack?
220  */
221 struct ecryptfs_crypt_stat {
         ...
236        size_t num_header_bytes_at_front;
237        size_t extent_size; /* Data extent size; default is 4096 */
         ...
252        struct mutex cs_mutex;
253 };

The important for us member here is the num_header_bytes_at_front unsigned integer. This is used to store the header length. For example, here is the initialization function for it:

1430 /**
1431  * set_default_header_data
1432  * @crypt_stat: The cryptographic context
1433  *
1434  * For version 0 file format; this function is only for backwards
1435  * compatibility for files created with the prior versions of
1436  * eCryptfs.
1437  */
1438 static void set_default_header_data(struct ecryptfs_crypt_stat *crypt_stat)
1439 {
1440        crypt_stat->num_header_bytes_at_front =
1441                ECRYPTFS_MINIMUM_HEADER_EXTENT_SIZE;
1442 }


Where as we saw earlier, this is defined at ecryptfs_kernel.h and it’s 8192. Now if we go back to ecryptfs_write_metadata() at line 1390 we’re going to see a function named ecryptfs_write_metadata_to_contents() being invoked. Judging by its name this should probably write the metadata to the contents but take a closer look at its parameters. It takes the following three arguments:
1) “struct ecryptfs_crypt_stat *” which is the actual context
2) “struct dentry *ecryptfs_dentry” which is the filesystem dentry
3) “char *virt” which is a pointer to the virtual address space allocated for the header
Now, take a look at this function:

1312 static int
1313 ecryptfs_write_metadata_to_contents(struct ecryptfs_crypt_stat *crypt_stat,
1314                                    struct dentry *ecryptfs_dentry,
1315                                    char *virt)
1316 {
1317        int rc;
1318
1319        rc = ecryptfs_write_lower(ecryptfs_dentry->d_inode, virt,
1320                                  0, crypt_stat->num_header_bytes_at_front);
1321        if (rc)
1322                printk(KERN_ERR "%s: Error attempting to write header "
1323                       "information to lower file; rc = [%d]\n", __func__,
1324                       rc);
1325        return rc;
1326 }

It calls ecryptfs_write_lower() and according to the documentation of this function:

27 /**
28  * ecryptfs_write_lower
29  * @ecryptfs_inode: The eCryptfs inode
30  * @data: Data to write
31  * @offset: Byte offset in the lower file to which to write the data
32  * @size: Number of bytes from @data to write at @offset in the lower
33  *        file
34  *
35  * Write data to the lower file.
36  *
37  * Returns zero on success; non-zero on error
38  */
39  int ecryptfs_write_lower(struct inode *ecryptfs_inode, char *data,
40                          loff_t offset, size_t size)

from fs/ecryptfs/read_write.c we can easily deduce that it’s incorrectly passing num_header_bytes_at_front as the size of bytes to be written even though this value is 8192 and our allocation was 4096 bytes. This can leak up to 4096 additional bytes from kernel memory. To patch this various changes where made. First of all, now fs/ecryptfs/crypto.c has a wrapper around get_zeroed_page() function which is this:

+ static unsigned long ecryptfs_get_zeroed_pages(gfp_t gfp_mask,
+                                              unsigned int order)
+ {
+       struct page *page;
+
+       page = alloc_pages(gfp_mask | __GFP_ZERO, order);
+       if (page)
+               return (unsigned long) page_address(page);
+       return 0;
+ }
+

In addition to this, ecryptfs_write_metadata_to_contents() was changed to contain an addition address space length parameter:

  static int
- ecryptfs_write_metadata_to_contents(struct ecryptfs_crypt_stat *crypt_stat,
-                                    struct dentry *ecryptfs_dentry,
-                                    char *virt)
+ ecryptfs_write_metadata_to_contents(struct dentry *ecryptfs_dentry,
+                                    char *virt, size_t virt_len)
  {

And its call to encrypt_write_lower() was changed to use this new variable as the size of bytes to be written like this:

        rc = ecryptfs_write_lower(ecryptfs_dentry->d_inode, virt,
-                                 0, crypt_stat->num_header_bytes_at_front);
+                                 0, virt_len);
        if (rc)

Then, ecryptfs_write_metadata() also updated to include those two new counters like this:

        struct ecryptfs_crypt_stat *crypt_stat =
                &ecryptfs_inode_to_private(ecryptfs_dentry->d_inode)->crypt_stat;
+       unsigned int order;
        char *virt;
+       size_t virt_len;
        size_t size = 0;
        int rc = 0;

And of course, its allocation does not longer use get_zeroed_page() but instead uses the above wrapper function like this:

        }
+       virt_len = crypt_stat->num_header_bytes_at_front;
+       order = get_order(virt_len);
        /* Released in this function */
-       virt = (char *)get_zeroed_page(GFP_KERNEL);
+       virt = (char *)ecryptfs_get_zeroed_pages(GFP_KERNEL, order);
        if (!virt) {

The call to ecryptfs_write_headers_virt() changed to include the virtual address size:

        }
-       rc = ecryptfs_write_headers_virt(virt, PAGE_CACHE_SIZE, &size,
-                                        crypt_stat, ecryptfs_dentry);
+       rc = ecryptfs_write_headers_virt(virt, virt_len, &size, crypt_stat,
+                                        ecryptfs_dentry);
        if (unlikely(rc)) {

And at last, the two functions called at lines 1387 and 1390 where updated like this:

        if (crypt_stat->flags & ECRYPTFS_METADATA_IN_XATTR)
-               rc = ecryptfs_write_metadata_to_xattr(ecryptfs_dentry,
-                                                     crypt_stat, virt, size);
+               rc = ecryptfs_write_metadata_to_xattr(ecryptfs_dentry, virt,
+                                                     size);
        else
-               rc = ecryptfs_write_metadata_to_contents(crypt_stat,
-                                                        ecryptfs_dentry, virt);
+               rc = ecryptfs_write_metadata_to_contents(ecryptfs_dentry, virt,
+                                                        virt_len);
        if (rc) {


Now, that everyone is using this cool virt_len the only thing left is to free the correct memory, so the out_free label changed to this:

        }
 out_free:
-       free_page((unsigned long)virt);
+       free_pages((unsigned long)virt, order);
 out:

That’s all with this nice little bug.

Written by xorl

April 1, 2009 at 15:18

Posted in bugs, linux

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s