If a swapfile is ftruncated while in use, subsequent swapout will scribble on the filesystem. This is a case of root-shoot-foot, but wrecking the fs is a fairly rude response. And it's easy to fix: hold i_sem across the life of the swapon. 25-akpm/mm/swapfile.c | 45 +++++++++++++++++++++++++++++---------------- 1 files changed, 29 insertions(+), 16 deletions(-) diff -puN mm/swapfile.c~swapfile-hold-i_sem mm/swapfile.c --- 25/mm/swapfile.c~swapfile-hold-i_sem Wed Apr 23 17:18:38 2003 +++ 25-akpm/mm/swapfile.c Wed Apr 23 17:30:06 2003 @@ -886,6 +886,10 @@ add_swap_extent(struct swap_info_struct * requirements, they are simply tossed out - we will never use those blocks * for swapping. * + * For S_ISREG swapfiles we hold i_sem across the life of the swapon. This + * prevents root from shooting her foot off by ftruncating an in-use swapfile, + * which will scribble on the fs. + * * The amount of disk space which a single swap extent represents varies. * Typically it is in the 1-4 megabyte range. So we can have hundreds of * extents in the list. To avoid much list walking, we cache the previous @@ -1095,6 +1099,8 @@ asmlinkage long sys_swapoff(const char _ bdev = swap_file->f_dentry->d_inode->i_bdev; set_blocksize(bdev, p->old_block_size); bd_release(bdev); + } else { + up(&swap_file->f_dentry->d_inode->i_mapping->host->i_sem); } filp_close(swap_file, NULL); err = 0; @@ -1228,6 +1234,8 @@ asmlinkage long sys_swapon(const char __ int swapfilesize; unsigned short *swap_map; struct page *page = NULL; + struct inode *inode; + struct inode *downed_inode = NULL; if (!capable(CAP_SYS_ADMIN)) return -EPERM; @@ -1274,39 +1282,42 @@ asmlinkage long sys_swapon(const char __ } p->swap_file = swap_file; + inode = swap_file->f_dentry->d_inode; + mapping = swap_file->f_dentry->d_inode->i_mapping; + + error = -EBUSY; + for (i = 0; i < nr_swapfiles; i++) { + struct swap_info_struct *q = &swap_info[i]; + + if (i == type || !q->swap_file) + continue; + if (mapping == q->swap_file->f_dentry->d_inode->i_mapping) + goto bad_swap; + } error = -EINVAL; - if (S_ISBLK(swap_file->f_dentry->d_inode->i_mode)) { - bdev = swap_file->f_dentry->d_inode->i_bdev; + if (S_ISBLK(inode->i_mode)) { + bdev = inode->i_bdev; error = bd_claim(bdev, sys_swapon); if (error < 0) { bdev = NULL; goto bad_swap; } p->old_block_size = block_size(bdev); - error = set_blocksize(swap_file->f_dentry->d_inode->i_bdev, - PAGE_SIZE); + error = set_blocksize(inode->i_bdev, PAGE_SIZE); if (error < 0) goto bad_swap; p->bdev = bdev; - } else if (S_ISREG(swap_file->f_dentry->d_inode->i_mode)) { - p->bdev = swap_file->f_dentry->d_inode->i_sb->s_bdev; + } else if (S_ISREG(inode->i_mode)) { + p->bdev = inode->i_sb->s_bdev; + downed_inode = mapping->host; + down(&downed_inode->i_sem); } else { goto bad_swap; } - mapping = swap_file->f_dentry->d_inode->i_mapping; swapfilesize = mapping->host->i_size >> PAGE_SHIFT; - error = -EBUSY; - for (i = 0 ; i < nr_swapfiles ; i++) { - struct swap_info_struct *q = &swap_info[i]; - if (i == type || !q->swap_file) - continue; - if (mapping == q->swap_file->f_dentry->d_inode->i_mapping) - goto bad_swap; - } - /* * Read the swap header. */ @@ -1452,6 +1463,8 @@ out: } if (name) putname(name); + if (error && downed_inode) + up(&downed_inode->i_sem); return error; } _