無料Wikiサービス | デモページ
検索

アクセス数
最近のコメント
kprobe - ななし
ksetの実装 - スーパーコピー
カーネルスレッドとは - ノース
カーネルスレッドとは - nbyst
asmlinkageってなに? - ノース
asmlinkageってなに? - よろしく
はじめ - ノース
はじめ - ノース
はじめ - 楽打連動ユーザー
はじめ - 楽打連動ユーザー
Adsense
広告情報が設定されていません。

vmsplice


vmspliceはユーザ空間メモリをpipeバッファにspliceすることで、ユーザ空間メモリをpipeバッファと共有します。ユーザ空間の仮想アドレスはカーネル空間で使用できません。仮想アドレスからpageを取得し、そのpageに改めてカーネル空間の仮想メモリを割り当てる事で実装しています。

pipeファイルidのfdが書き込みならvmsplice_to_pipe()、読み込みならvmsplice_to_user()をコールします。
SYSCALL_DEFINE4(vmsplice, int, fd, const struct iovec __user *, iov,
               unsigned long, nr_segs, unsigned int, flags)
{
       struct file *file;
       long error;
       int fput;

       if (unlikely(nr_segs > UIO_MAXIOV))
               return -EINVAL;
       else if (unlikely(!nr_segs))
               return 0;

       error = -EBADF;
       file = fget_light(fd, &fput);
       if (file) {
               if (file->f_mode & FMODE_WRITE)
                       error = vmsplice_to_pipe(file, iov, nr_segs, flags);
               else if (file->f_mode & FMODE_READ)
                       error = vmsplice_to_user(file, iov, nr_segs, flags);

               fput_light(file, fput);
       }

       return error;
}
pipeからユーザ空間メモリで書き出す処理です。splice_grow_spd()はpipeバッファサイズに応じて、sdp->page/partialを取得設定します。get_iovec_page_array()で、iovで設定しているのユーザ空間メモリのpageを、spd.pagesに取り出して、splice_to_pipe()でpipeのデータをsdpのそのpageに書き出します。
static long vmsplice_to_pipe(struct file *file, const struct iovec __user *iov,
                            unsigned long nr_segs, unsigned int flags)
{
       struct pipe_inode_info *pipe;
       struct page *pages[PIPE_DEF_BUFFERS];
       struct partial_page partial[PIPE_DEF_BUFFERS];
       struct splice_pipe_desc spd = {
               .pages = pages,
               .partial = partial,
               .flags = flags,
               .ops = &user_page_pipe_buf_ops,
               .spd_release = spd_release_page,
       };
       long ret;

       pipe = get_pipe_info(file);
       if (!pipe)
               return -EBADF;

       if (splice_grow_spd(pipe, &spd))
               return -ENOMEM;

       spd.nr_pages = get_iovec_page_array(iov, nr_segs, spd.pages,
                                           spd.partial, flags & SPLICE_F_GIFT,
                                           pipe->buffers);
       if (spd.nr_pages <= 0)
               ret = spd.nr_pages;
       else
               ret = splice_to_pipe(pipe, &spd);

       splice_shrink_spd(pipe, &spd);
       return ret;
}
まず、カーネルパスではユーザ空間を参照するため、iovのentryをカーネル空間にコピーして処理します。base/lenでそのアドレス/サイズを引数とし、get_user_pages_fast()をコールします。この関数でそのpageをpages[buffers]に取得し、offset/lenを設定します。

alignedはオプションSPLICE_F_GIFTが設定されていれば、1となり、この場合entryはpageサイズ単位でないといけません。splice_to_pipe()でpipeバッファーとspdでリンクする時、複写するか移動するかの処理があって、SPLICE_F_GIFTは移動となり、従ってpageサイズに満たない領域は、他で使われていることがあります。そうなるといけません。そのためpageサイズ全てが他に使われてないことが保障されなければなりません。
static int get_iovec_page_array(const struct iovec __user *iov,
                               unsigned int nr_vecs, struct page **pages,
                               struct partial_page *partial, int aligned,
                               unsigned int pipe_buffers)
{
       int buffers = 0, error = 0;

       while (nr_vecs) {
               unsigned long off, npages;
               struct iovec entry;
               void __user *base;
               size_t len;
               int i;

               error = -EFAULT;
               if (copy_from_user(&entry, iov, sizeof(entry)))
                       break;

               base = entry.iov_base;
               len = entry.iov_len;

               error = 0;
               if (unlikely(!len))
                       break;
               error = -EFAULT;
               if (!access_ok(VERIFY_READ, base, len))
                       break;

               off = (unsigned long) base & ~PAGE_MASK;

               error = -EINVAL;
               if (aligned && (off || len & ~PAGE_MASK))
                       break;

               npages = (off + len + PAGE_SIZE - 1) >> PAGE_SHIFT;
               if (npages > pipe_buffers - buffers)
                       npages = pipe_buffers - buffers;

               error = get_user_pages_fast((unsigned long)base, npages,
                                       0, &pages[buffers]);

               if (unlikely(error <= 0))
                       break;

               for (i = 0; i < error; i++) {
                       const int plen = min_t(size_t, len, PAGE_SIZE - off);

                       partial[buffers].offset = off;
                       partial[buffers].len = plen;

                       off = 0;
                       len -= plen;
                       buffers++;
               }

               if (len)
                       break;

               if (error < npages || buffers == pipe_buffers)
                       break;

               nr_vecs--;
               iov++;
       }

       if (buffers)
               return buffers;

       return error;
}

補足

vmspliceのroot権限が奪われる。という脆弱性があったようです。const struct iovec __user *iov内にtask_structのeuidを書き換えてrootになるのようなスタックからcurrent構造体を取得し、uidを更新する。というコードを埋め込み、vmspliceをコールした後、シェルを起動するような一連の処理を行えばいいのでしょうが、問題は、スタックオーバフロと違って、いかにiovからカーネル空間にマップされたコードを実行するかです。あるHPに、NULLポインタを参照すれば例外が発生する云々と言う記載があったのですが、そうなるとその例外処理のアドレスにマップする必要があるのではと・・・。、この辺りどのようになっているのでしょうか? Linux3ではこの辺りは対応されているようで、いまひとつ読み取ることができません。


最終更新 2014/04/11 17:38:22 - north
(2014/04/11 17:38:22 作成)