ulimit -s unlimitedによる32bit ASLR無効化

一般に、ASLRはsysctlコマンドなどでカーネルパラメータを変更することにより有効化・無効化できる。

$ sudo sysctl -w kernel.randomize_va_space=2  # enable
kernel.randomize_va_space = 2

$ sudo sysctl -w kernel.randomize_va_space=0  # disable
kernel.randomize_va_space = 0

この操作にはroot権限が必要であり、一般ユーザの権限では変更できない。

起動時にsetarchコマンドを使うことで、特定のプロセスのみASLRを無効化する方法もある。

$ setarch i686 -R ./a.out

この操作はroot権限を必要としないが、通常の手順で起動されるプロセスには適用できない。

しかし32bit環境の場合、rlimitコマンドでスタックサイズを無限にすると、以降に起動されるプロセスすべてでASLRが無効になる。

$ ulimit -s unlimited

この操作は一般ユーザ権限でも行うことができる。 また、プログラム中でsetrlimit(RLIMIT_STACK, {RLIM_INFINITY, RLIM_INFINITY})を呼ぶことでも同じ効果が得られる。 なお、64bit環境の場合にはASLRは無効にならない。

これは、Linuxカーネルの実装によるものであり、具体的には次のコードに対応する。

static int mmap_is_legacy(void)
{
        if (current->personality & ADDR_COMPAT_LAYOUT)
                return 1;

        if (rlimit(RLIMIT_STACK) == RLIM_INFINITY)
                return 1;

        return sysctl_legacy_va_layout;
}

RLIMIT_STACKがRLIM_INFINITYの場合、mmap_is_legacyはtrueとなる。 そして、mmap_is_legacyは次の箇所で呼ばれている。

/*
 * This function, called very early during the creation of a new
 * process VM image, sets up which VM layout function to use:
 */
void arch_pick_mmap_layout(struct mm_struct *mm)
{
        mm->mmap_legacy_base = mmap_legacy_base();
        mm->mmap_base = mmap_base();

        if (mmap_is_legacy()) {
                mm->mmap_base = mm->mmap_legacy_base;
                mm->get_unmapped_area = arch_get_unmapped_area;
        } else {
                mm->get_unmapped_area = arch_get_unmapped_area_topdown;
        }
}

mm->mmap_baseは、mmap_is_legacyがtrueの場合mmap_legacy_base()の返り値、falseの場合mmap_base()の返り値となっている。 これらの実装は次のようになっている。

static unsigned long mmap_rnd(void)
{
        unsigned long rnd = 0;

        /*
        *  8 bits of randomness in 32bit mmaps, 20 address space bits
        * 28 bits of randomness in 64bit mmaps, 40 address space bits
        */
        if (current->flags & PF_RANDOMIZE) {
                if (mmap_is_ia32())
                        rnd = get_random_int() % (1<<8);
                else
                        rnd = get_random_int() % (1<<28);
        }
        return rnd << PAGE_SHIFT;
}

static unsigned long mmap_base(void)
{
        unsigned long gap = rlimit(RLIMIT_STACK);

        if (gap < MIN_GAP)
                gap = MIN_GAP;
        else if (gap > MAX_GAP)
                gap = MAX_GAP;

        return PAGE_ALIGN(TASK_SIZE - gap - mmap_rnd());
}

/*
 * Bottom-up (legacy) layout on X86_32 did not support randomization, X86_64
 * does, but not when emulating X86_32
 */
static unsigned long mmap_legacy_base(void)
{
        if (mmap_is_ia32())
                return TASK_UNMAPPED_BASE;
        else
                return TASK_UNMAPPED_BASE + mmap_rnd();
}

上の内容から、mmap_is_ia32()がtrue、つまり32bit環境の場合mmap_legacy_base()が返すアドレスが固定となっていることがわかる。 また、mmap_rnd()の実装から、32bit環境では8bit、64bit環境では28bitのランダム化が行われていることがわかる。

追記(2016-11-16)

この動作はLinux kernel 4.5で修正された。

関連リンク