ファイルマップピング/無名メモリーリージョンの獲得
ヒープ領域でファイルマップピング/無名メモリーリージョンはスタックの取り扱いにより、アロケーションが異なっていることが分かりました。実装の面からその辺りを調べてみました。メモリーリージョン獲得はdo_mmap関数で行われます。そこからdo_mmap_pgoff関数をコールし、そこからget_unmapped_area_prot関数で確保できるアドレスを取得します。そしてそのアドレスから実際のメモリーリージョンを確保することになります。
(get_unmapped_exec_areaは、exec-shieldを考慮しての処理かと思いますがよく分かりません。)
ではcurrent->mm->get_unmapped_exec_area/current->mm->get_unmapped_areaはどこで設定されるかということですが、これはプロセスのロード関数load_elf_binary関数から、arch_pick_mmap_layout関数を呼び出すことで設定されているようです。
exec_shieldはsetup-exec_shieldをコントロールするパブリック変数で、ビット2はソースコメント上では、force noexecstack regardless of PT_GNU_STACKと言うことです。PT_GNU_STACKはなんかプログラムヘッダーでもスタック上でコードが実行できるかどうか設定できるようで、もしexec_shieldのビット2が設定されていると、それを無視してコードのスタック上での実行を不可とするようです。
で、スタックサイズ指定なし(レガシー)でも、exec_shieldのビット2がONなら、レガシーでない処理となるようで、mm->mmap_base はスタックの上からトップダウンとなっているようです。メモリ割り当て関数としてarch_get_unmapped_exec_area関数を設定するのは、なんとなく推測できますが、スタック上でコードが実行させないために、トップダウンとなるのか疑問です。
補足)
arch_get_unmapped_exec_area関数がトップダウンで割り当てているのか調べていませんが、mm->mmap_baseの値はスタックの上から(アドレス的には低い)値で、従ってトップダウンと。
要はmmap_legacy_base/mmap_baseで、従来型は0x40000000から、そうでない場合スタックの上からメモリリージョンが獲得されていくわけです。
なおメモリーリージョン獲得において、従来型の場合arch_get_unmapped_area関数が、そうでない場合arch_get_unmapped_area_topdown関数が呼ばれるわけですが、その獲得ベースとなるmm->mmap_baseは、arch_get_unmapped_area関数では参照されていないようで、arch_get_unmapped_area関数では、find_start_end関数から検索開始アドレスと終了アドレ取得し、その範囲内でメモリーリージョンを確保しているようです。そしてfind_start_end関数はその値をmm->mmap_baseからでなく、固定的に返しているようです。
unsigned long get_unmapped_area_prot(
struct file *file, unsigned long addr, unsigned long len,
unsigned long pgoff, unsigned long flags, int exec)
{
unsigned long (*get_area)(struct file *, unsigned long,
unsigned long, unsigned long, unsigned long);
if (exec && current->mm->get_unmapped_exec_area)
get_area = current->mm->get_unmapped_exec_area;
else
get_area = current->mm->get_unmapped_area;
if (file && file->f_op && file->f_op->get_unmapped_area)
get_area = file->f_op->get_unmapped_area;
addr = get_area(file, addr, len, pgoff, flags);
if (IS_ERR_VALUE(addr))
return addr;
if (addr > TASK_SIZE - len)
return -ENOMEM;
if (addr & ~PAGE_MASK)
return -EINVAL;
return arch_rebalance_pgtables(addr, len);
}
ファイルマッピング、無名メモリーリージョンで処理する関数が異なります。get_unmapped_area_prot関数では、まずそのコールバック関数を設定します。ファイルメモリマップではファイルディスクリプターのオペレーションから(file->f_op->get_unmapped_area)、無名メモリーリージョンではcurrent->mmのget_unmapped_exec_areaないしmm->get_unmapped_areaを、から取得しています。そして、そのどれかを関数型ローカル変数get_areaに設定し、それをコールすることで確保できるメモリーリージョンのアドレスを取得し、そのアドレスで実際のメモリーリージョンを確保すると言う具合です。(get_unmapped_exec_areaは、exec-shieldを考慮しての処理かと思いますがよく分かりません。)
ではcurrent->mm->get_unmapped_exec_area/current->mm->get_unmapped_areaはどこで設定されるかということですが、これはプロセスのロード関数load_elf_binary関数から、arch_pick_mmap_layout関数を呼び出すことで設定されているようです。
root/arch/x86/mm/mmap.c
void arch_pick_mmap_layout(struct mm_struct *mm)
{
if (!(2 & exec_shield) && mmap_is_legacy()) {
mm->mmap_base = mmap_legacy_base();
mm->get_unmapped_area = arch_get_unmapped_area;
mm->unmap_area = arch_unmap_area;
} else {
mm->mmap_base = mmap_base();
mm->get_unmapped_area = arch_get_unmapped_area_topdown;
if (!(current->personality & READ_IMPLIES_EXEC)
&& mmap_is_ia32())
mm->get_unmapped_exec_area = arch_get_unmapped_exec_area;
mm->unmap_area = arch_unmap_area_topdown;
}
}
mmap_is_legacy関数でスタックサイズの指定によりどちらのメモリ獲得関数を適用するか判断しています。スタックサイズが無限(current->signal->rlim[RLIMIT_STACK].rlim_cur == RLIM_INFINITY)の場合、従来型ということです。この場合arch_get_unmapped_areaが、そうで無い場合arch_get_unmapped_area_topdownが設定されることになります。exec_shieldはsetup-exec_shieldをコントロールするパブリック変数で、ビット2はソースコメント上では、force noexecstack regardless of PT_GNU_STACKと言うことです。PT_GNU_STACKはなんかプログラムヘッダーでもスタック上でコードが実行できるかどうか設定できるようで、もしexec_shieldのビット2が設定されていると、それを無視してコードのスタック上での実行を不可とするようです。
で、スタックサイズ指定なし(レガシー)でも、exec_shieldのビット2がONなら、レガシーでない処理となるようで、mm->mmap_base はスタックの上からトップダウンとなっているようです。メモリ割り当て関数としてarch_get_unmapped_exec_area関数を設定するのは、なんとなく推測できますが、スタック上でコードが実行させないために、トップダウンとなるのか疑問です。
補足)
arch_get_unmapped_exec_area関数がトップダウンで割り当てているのか調べていませんが、mm->mmap_baseの値はスタックの上から(アドレス的には低い)値で、従ってトップダウンと。
static int mmap_is_legacy(void)
{
:
if (current->signal->rlim[RLIMIT_STACK].rlim_cur == RLIM_INFINITY)
return 1;
:
}
mm->mmap_baseはメモリーマップ割り当てときのベースとなるアドレスです。従来型の場合mmap_legacy_base関数を、そうでない場合、mmap_base関数で設定しています。mmap_legacy_baseは基本的にはTASK_UNMAPPED_BASEを返しているだけです。これはシステムに依存する値ですが、X86ではタスクサイズ(3G)の1/3、すなわち0x40000000ということです。
define TASK_UNMAPPED_BASE (PAGE_ALIGN(TASK_SIZE / 3))
static unsigned long mmap_legacy_base(void)
{
if (mmap_is_ia32())
return TASK_UNMAPPED_BASE;
else
return TASK_UNMAPPED_BASE + mmap_rnd();
}
レガシーでないmm->mmap_baseは、スタックサイズからギャップ分の範囲を考慮した値を、TASK_SIZE(0xc0000000)- gap - mmap_rnd()として返しています。
#define MAX_GAP (TASK_SIZE/6*5)
static unsigned long mmap_base(void)
{
unsigned long gap = current->signal->rlim[RLIMIT_STACK].rlim_cur;
if (gap < MIN_GAP)
gap = MIN_GAP;
else if (gap > MAX_GAP)
gap = MAX_GAP;
return PAGE_ALIGN(TASK_SIZE - gap - mmap_rnd());
}
mmap_rndはcurrent->flags & PF_RANDOMIZEなら、乱数から得られた値を上乗せしてその値としています。スタックサイズを固定で設定しても、プロセス毎にばらつきを持たせることで、セキュリティを高めているためと認識しています。要はmmap_legacy_base/mmap_baseで、従来型は0x40000000から、そうでない場合スタックの上からメモリリージョンが獲得されていくわけです。
なおメモリーリージョン獲得において、従来型の場合arch_get_unmapped_area関数が、そうでない場合arch_get_unmapped_area_topdown関数が呼ばれるわけですが、その獲得ベースとなるmm->mmap_baseは、arch_get_unmapped_area関数では参照されていないようで、arch_get_unmapped_area関数では、find_start_end関数から検索開始アドレスと終了アドレ取得し、その範囲内でメモリーリージョンを確保しているようです。そしてfind_start_end関数はその値をmm->mmap_baseからでなく、固定的に返しているようです。
static void find_start_end(unsigned long flags, unsigned long *begin,
unsigned long *end)
{
if (!test_thread_flag(TIF_IA32) && (flags & MAP_32BIT)) {
unsigned long new_begin;
*begin = 0x40000000;
*end = 0x80000000;
if (current->flags & PF_RANDOMIZE) {
new_begin = randomize_range(*begin, *begin + 0x02000000, 0);
if (new_begin)
*begin = new_begin;
}
} else {
*begin = TASK_UNMAPPED_BASE;
*end = TASK_SIZE;
}
}
ここでは無名メモリーリージョンについて調べてみましたが、ファイルマップについても、アロケーション位置については同じようなことが行われいるのではと推測していますが・・・。



