| From: Jan Kara <jack@suse.cz> |
| Date: Thu, 18 Dec 2014 22:37:50 +0100 |
| Subject: udf: Check path length when reading symlink |
| |
| commit 0e5cc9a40ada6046e6bc3bdfcd0c0d7e4b706b14 upstream. |
| |
| Symlink reading code does not check whether the resulting path fits into |
| the page provided by the generic code. This isn't as easy as just |
| checking the symlink size because of various encoding conversions we |
| perform on path. So we have to check whether there is still enough space |
| in the buffer on the fly. |
| |
| Reported-by: Carl Henrik Lunde <chlunde@ping.uio.no> |
| Signed-off-by: Jan Kara <jack@suse.cz> |
| [bwh: Backported to 3.2: adjust context] |
| Signed-off-by: Ben Hutchings <ben@decadent.org.uk> |
| --- |
| fs/udf/dir.c | 3 ++- |
| fs/udf/namei.c | 3 ++- |
| fs/udf/symlink.c | 31 ++++++++++++++++++++++++++----- |
| fs/udf/udfdecl.h | 3 ++- |
| fs/udf/unicode.c | 28 ++++++++++++++++------------ |
| 5 files changed, 48 insertions(+), 20 deletions(-) |
| |
| --- a/fs/udf/dir.c |
| +++ b/fs/udf/dir.c |
| @@ -163,7 +163,8 @@ static int do_udf_readdir(struct inode * |
| struct kernel_lb_addr tloc = lelb_to_cpu(cfi.icb.extLocation); |
| |
| iblock = udf_get_lb_pblock(dir->i_sb, &tloc, 0); |
| - flen = udf_get_filename(dir->i_sb, nameptr, fname, lfi); |
| + flen = udf_get_filename(dir->i_sb, nameptr, lfi, fname, |
| + UDF_NAME_LEN); |
| dt_type = DT_UNKNOWN; |
| } |
| |
| --- a/fs/udf/namei.c |
| +++ b/fs/udf/namei.c |
| @@ -235,7 +235,8 @@ static struct fileIdentDesc *udf_find_en |
| if (!lfi) |
| continue; |
| |
| - flen = udf_get_filename(dir->i_sb, nameptr, fname, lfi); |
| + flen = udf_get_filename(dir->i_sb, nameptr, lfi, fname, |
| + UDF_NAME_LEN); |
| if (flen && udf_match(flen, fname, child->len, child->name)) |
| goto out_ok; |
| } |
| --- a/fs/udf/symlink.c |
| +++ b/fs/udf/symlink.c |
| @@ -30,13 +30,16 @@ |
| #include <linux/buffer_head.h> |
| #include "udf_i.h" |
| |
| -static void udf_pc_to_char(struct super_block *sb, unsigned char *from, |
| - int fromlen, unsigned char *to) |
| +static int udf_pc_to_char(struct super_block *sb, unsigned char *from, |
| + int fromlen, unsigned char *to, int tolen) |
| { |
| struct pathComponent *pc; |
| int elen = 0; |
| + int comp_len; |
| unsigned char *p = to; |
| |
| + /* Reserve one byte for terminating \0 */ |
| + tolen--; |
| while (elen < fromlen) { |
| pc = (struct pathComponent *)(from + elen); |
| switch (pc->componentType) { |
| @@ -49,22 +52,37 @@ static void udf_pc_to_char(struct super_ |
| break; |
| /* Fall through */ |
| case 2: |
| + if (tolen == 0) |
| + return -ENAMETOOLONG; |
| p = to; |
| *p++ = '/'; |
| + tolen--; |
| break; |
| case 3: |
| + if (tolen < 3) |
| + return -ENAMETOOLONG; |
| memcpy(p, "../", 3); |
| p += 3; |
| + tolen -= 3; |
| break; |
| case 4: |
| + if (tolen < 2) |
| + return -ENAMETOOLONG; |
| memcpy(p, "./", 2); |
| p += 2; |
| + tolen -= 2; |
| /* that would be . - just ignore */ |
| break; |
| case 5: |
| - p += udf_get_filename(sb, pc->componentIdent, p, |
| - pc->lengthComponentIdent); |
| + comp_len = udf_get_filename(sb, pc->componentIdent, |
| + pc->lengthComponentIdent, |
| + p, tolen); |
| + p += comp_len; |
| + tolen -= comp_len; |
| + if (tolen == 0) |
| + return -ENAMETOOLONG; |
| *p++ = '/'; |
| + tolen--; |
| break; |
| } |
| elen += sizeof(struct pathComponent) + pc->lengthComponentIdent; |
| @@ -73,6 +91,7 @@ static void udf_pc_to_char(struct super_ |
| p[-1] = '\0'; |
| else |
| p[0] = '\0'; |
| + return 0; |
| } |
| |
| static int udf_symlink_filler(struct file *file, struct page *page) |
| @@ -108,8 +127,10 @@ static int udf_symlink_filler(struct fil |
| symlink = bh->b_data; |
| } |
| |
| - udf_pc_to_char(inode->i_sb, symlink, inode->i_size, p); |
| + err = udf_pc_to_char(inode->i_sb, symlink, inode->i_size, p, PAGE_SIZE); |
| brelse(bh); |
| + if (err) |
| + goto out_unlock_inode; |
| |
| up_read(&iinfo->i_data_sem); |
| SetPageUptodate(page); |
| --- a/fs/udf/udfdecl.h |
| +++ b/fs/udf/udfdecl.h |
| @@ -207,7 +207,8 @@ udf_get_lb_pblock(struct super_block *sb |
| } |
| |
| /* unicode.c */ |
| -extern int udf_get_filename(struct super_block *, uint8_t *, uint8_t *, int); |
| +extern int udf_get_filename(struct super_block *, uint8_t *, int, uint8_t *, |
| + int); |
| extern int udf_put_filename(struct super_block *, const uint8_t *, uint8_t *, |
| int); |
| extern int udf_build_ustr(struct ustr *, dstring *, int); |
| --- a/fs/udf/unicode.c |
| +++ b/fs/udf/unicode.c |
| @@ -28,7 +28,8 @@ |
| |
| #include "udf_sb.h" |
| |
| -static int udf_translate_to_linux(uint8_t *, uint8_t *, int, uint8_t *, int); |
| +static int udf_translate_to_linux(uint8_t *, int, uint8_t *, int, uint8_t *, |
| + int); |
| |
| static int udf_char_to_ustr(struct ustr *dest, const uint8_t *src, int strlen) |
| { |
| @@ -333,8 +334,8 @@ try_again: |
| return u_len + 1; |
| } |
| |
| -int udf_get_filename(struct super_block *sb, uint8_t *sname, uint8_t *dname, |
| - int flen) |
| +int udf_get_filename(struct super_block *sb, uint8_t *sname, int slen, |
| + uint8_t *dname, int dlen) |
| { |
| struct ustr *filename, *unifilename; |
| int len = 0; |
| @@ -347,7 +348,7 @@ int udf_get_filename(struct super_block |
| if (!unifilename) |
| goto out1; |
| |
| - if (udf_build_ustr_exact(unifilename, sname, flen)) |
| + if (udf_build_ustr_exact(unifilename, sname, slen)) |
| goto out2; |
| |
| if (UDF_QUERY_FLAG(sb, UDF_FLAG_UTF8)) { |
| @@ -366,7 +367,8 @@ int udf_get_filename(struct super_block |
| } else |
| goto out2; |
| |
| - len = udf_translate_to_linux(dname, filename->u_name, filename->u_len, |
| + len = udf_translate_to_linux(dname, dlen, |
| + filename->u_name, filename->u_len, |
| unifilename->u_name, unifilename->u_len); |
| out2: |
| kfree(unifilename); |
| @@ -403,10 +405,12 @@ int udf_put_filename(struct super_block |
| #define EXT_MARK '.' |
| #define CRC_MARK '#' |
| #define EXT_SIZE 5 |
| +/* Number of chars we need to store generated CRC to make filename unique */ |
| +#define CRC_LEN 5 |
| |
| -static int udf_translate_to_linux(uint8_t *newName, uint8_t *udfName, |
| - int udfLen, uint8_t *fidName, |
| - int fidNameLen) |
| +static int udf_translate_to_linux(uint8_t *newName, int newLen, |
| + uint8_t *udfName, int udfLen, |
| + uint8_t *fidName, int fidNameLen) |
| { |
| int index, newIndex = 0, needsCRC = 0; |
| int extIndex = 0, newExtIndex = 0, hasExt = 0; |
| @@ -440,7 +444,7 @@ static int udf_translate_to_linux(uint8_ |
| newExtIndex = newIndex; |
| } |
| } |
| - if (newIndex < 256) |
| + if (newIndex < newLen) |
| newName[newIndex++] = curr; |
| else |
| needsCRC = 1; |
| @@ -468,13 +472,13 @@ static int udf_translate_to_linux(uint8_ |
| } |
| ext[localExtIndex++] = curr; |
| } |
| - maxFilenameLen = 250 - localExtIndex; |
| + maxFilenameLen = newLen - CRC_LEN - localExtIndex; |
| if (newIndex > maxFilenameLen) |
| newIndex = maxFilenameLen; |
| else |
| newIndex = newExtIndex; |
| - } else if (newIndex > 250) |
| - newIndex = 250; |
| + } else if (newIndex > newLen - CRC_LEN) |
| + newIndex = newLen - CRC_LEN; |
| newName[newIndex++] = CRC_MARK; |
| valueCRC = crc_itu_t(0, fidName, fidNameLen); |
| newName[newIndex++] = hexChar[(valueCRC & 0xf000) >> 12]; |