Received: by 2002:a05:6a10:f3d0:0:0:0:0 with SMTP id a16csp1083237pxv; Thu, 1 Jul 2021 17:00:23 -0700 (PDT) X-Google-Smtp-Source: ABdhPJxiptogS77BnGhA6o1U6VmeoABc6uliYrPGyQQoPETTNcE6BMAJ5K21JdVU3owvBgEPBl23 X-Received: by 2002:a05:6402:d0a:: with SMTP id eb10mr3130302edb.139.1625184023119; Thu, 01 Jul 2021 17:00:23 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1625184023; cv=none; d=google.com; s=arc-20160816; b=nAFnhHR6zV+sxMWM5LbvfX3RugUg3fShNUJABqyJhWA6jYTqq4MMEPBB2l/Q09KH5U f6Yw7f7R+zqaPppBx/IKW2JsEjQmMHEy8v4aEo1WCTrR0uzkYEXtEYnDwcyGeF2BF4so Yp+d7vhtKTiHB7bWMNKihkM8noIp+Z/F3NYYuET14Ot/rxDtKIr0HCSRH1DvXv0TS7+m vaKYm55ZE2rhwveL3eoGTxXmNd+r4N3rUgBHXAPNVMYXTJpExDyFtEpviC667NyGw4Lz MQ767A5+gRszmwN3Chvft/SKJehv3F/D5Qq5Redl+XVH6qo2j5W0eFwy7Z7WgGxhyBDT R5Tg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :message-id:subject:reply-to:cc:from:to:dkim-signature:date; bh=vHKTUFhjrBhE16qbi5KjG4YoGMyuu2TJDAhJMIvSDHc=; b=t3KVYCSjNtMliG0bMSkziNalMNJWj4Mhcz4OkRHRHu6sOPG6c+CSuijuuH/5ACHILZ A3tYYmFj80Fzu0LwoVUC05INnfDsmcXgfQNpaxWObYoqkLQFgRiZJrK9WsToioKz5Rm3 HR21YDG4nFry8h2kRv6uG6cXPEpCRQNb/38iu7nXgSZmv/HvEAgbWR2owGizaoVaggJe +70nLpgLvVDAp+cjqeqmG6gHHVNEFhvPgvX894BGmOM/uHsUgV+v6J1SYAr18Y2z5Z9L DY8TPT7JoEkYcy4iUcknqj9r/PYFr7+iY1fNpnXCcEcLAT341W6i/pqt+WGtXiO0hV5R 48Ng== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@pm.me header.s=protonmail header.b="ZWl1K/du"; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=pm.me Return-Path: Received: from vger.kernel.org (vger.kernel.org. [23.128.96.18]) by mx.google.com with ESMTP id w13si1401847ejk.30.2021.07.01.16.59.56; Thu, 01 Jul 2021 17:00:23 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) client-ip=23.128.96.18; Authentication-Results: mx.google.com; dkim=pass header.i=@pm.me header.s=protonmail header.b="ZWl1K/du"; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=pm.me Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S234337AbhGAX6f (ORCPT + 99 others); Thu, 1 Jul 2021 19:58:35 -0400 Received: from mail-0201.mail-europe.com ([51.77.79.158]:41754 "EHLO mail-0201.mail-europe.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S234063AbhGAX6f (ORCPT ); Thu, 1 Jul 2021 19:58:35 -0400 Date: Thu, 01 Jul 2021 23:55:14 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pm.me; s=protonmail; t=1625183728; bh=vHKTUFhjrBhE16qbi5KjG4YoGMyuu2TJDAhJMIvSDHc=; h=Date:To:From:Cc:Reply-To:Subject:From; b=ZWl1K/duuR6AkGbFqdy6cxxNtf7pKljObHGE4X/oR6EYdDyUwWCwF2Ff/bEdjc9nX ef/FbVZ++YU88xuXpiILi13tx+OleonACFBno8YKNjjiKfNTD0zGi/qFQzQvSjRHyJ 4ZWqP8fqn77rZH37BgnXHbwSt20DAWMhVpdgLvjTOQhfS9WlQ6UZmhaeYxxxmLJZP9 rHo2jA5qUmZnHl6/dCDgriCLD8rKyIsu1vFt8y2qlUhsd/OgtjpZ2oaMhsJRzv1aO+ vzPRrkg/9OdFkwIV2Pk3+FSTQ1/RAtbrOfbUMHwDRlq54u89NrK8RoJsrvDeWwfvIO ewHvePbW8p0tQ== To: John Wood From: Alexander Lobakin Cc: Alexander Lobakin , Kees Cook , Jann Horn , Jonathan Corbet , James Morris , "Serge E. Hallyn" , Shuah Khan , Thomas Gleixner , Ingo Molnar , Borislav Petkov , x86@kernel.org, "H. Peter Anvin" , Arnd Bergmann , Andi Kleen , valdis.kletnieks@vt.edu, Greg Kroah-Hartman , Randy Dunlap , Andrew Morton , linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-arch@vger.kernel.org, linux-hardening@vger.kernel.org, kernel-hardening@lists.openwall.com Reply-To: Alexander Lobakin Subject: Re: [PATCH v8 3/8] security/brute: Detect a brute force attack Message-ID: <20210701234807.50453-1-alobakin@pm.me> MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Spam-Status: No, score=-1.2 required=10.0 tests=ALL_TRUSTED,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF shortcircuit=no autolearn=disabled version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on mailout.protonmail.ch Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Hi, From: John Wood Date: Sat, 5 Jun 2021 17:04:00 +0200 > For a correct management of a fork brute force attack it is necessary to > track all the information related to the application crashes. To do so, > use the extended attributes (xattr) of the executable files and define a > statistical data structure to hold all the necessary information shared > by all the fork hierarchy processes. This info is the number of crashes, > the last crash timestamp and the crash period's moving average. > > The same can be achieved using a pointer to the fork hierarchy > statistical data held by the task_struct structure. But this has an > important drawback: a brute force attack that happens through the execve > system call losts the faults info since these statistics are freed when > the fork hierarchy disappears. Using this method makes not possible to > manage this attack type that can be successfully treated using extended > attributes. > > Also, to avoid false positives during the attack detection it is > necessary to narrow the possible cases. So, only the following scenarios > are taken into account: > > 1.- Launching (fork()/exec()) a setuid/setgid process repeatedly until a > desirable memory layout is got (e.g. Stack Clash). > 2.- Connecting to an exec()ing network daemon (e.g. xinetd) repeatedly > until a desirable memory layout is got (e.g. what CTFs do for simple > network service). > 3.- Launching processes without exec() (e.g. Android Zygote) and > exposing state to attack a sibling. > 4.- Connecting to a fork()ing network daemon (e.g. apache) repeatedly > until the previously shared memory layout of all the other children > is exposed (e.g. kind of related to HeartBleed). > > In each case, a privilege boundary has been crossed: > > Case 1: setuid/setgid process > Case 2: network to local > Case 3: privilege changes > Case 4: network to local > > To mark that a privilege boundary has been crossed it is only necessary > to create a new stats for the executable file via the extended attribute > and only if it has no previous statistical data. This is done using four > different LSM hooks, one per privilege boundary: > > setuid/setgid process --> bprm_creds_from_file hook (based on secureexec > flag). > network to local -------> socket_accept hook (taking into account only > external connections). > privilege changes ------> task_fix_setuid and task_fix_setgid hooks. > > To detect a brute force attack it is necessary that the executable file > statistics be updated in every fatal crash and the most important data > to update is the application crash period. To do so, use the new > "task_fatal_signal" LSM hook added in a previous step. > > The application crash period must be a value that is not prone to change > due to spurious data and follows the real crash period. So, to compute > it, the exponential moving average (EMA) is used. > > Based on the updated statistics two different attacks can be handled. A > slow brute force attack that is detected if the maximum number of faults > per fork hierarchy is reached and a fast brute force attack that is > detected if the application crash period falls below a certain > threshold. > > Moreover, only the signals delivered by the kernel are taken into > account with the exception of the SIGABRT signal since the latter is > used by glibc for stack canary, malloc, etc failures, which may indicate > that a mitigation has been triggered. > > Signed-off-by: John Wood > > > > +static int brute_get_xattr_stats(struct dentry *dentry, struct inode *in= ode, > +=09=09=09=09 struct brute_stats *stats) > +{ > +=09int rc; > +=09struct brute_raw_stats raw_stats; > + > +=09rc =3D __vfs_getxattr(dentry, inode, XATTR_NAME_BRUTE, &raw_stats, > +=09=09=09 sizeof(raw_stats)); > +=09if (rc < 0) > +=09=09return rc; > + > +=09stats->faults =3D le32_to_cpu(raw_stats.faults); > +=09stats->nsecs =3D le64_to_cpu(raw_stats.nsecs); > +=09stats->period =3D le64_to_cpu(raw_stats.period); > +=09stats->flags =3D raw_stats.flags; > +=09return 0; > +} > > > > +static int brute_task_execve(struct linux_binprm *bprm, struct file *fil= e) > +{ > +=09struct dentry *dentry =3D file_dentry(bprm->file); > +=09struct inode *inode =3D file_inode(bprm->file); > +=09struct brute_stats stats; > +=09int rc; > + > +=09inode_lock(inode); > +=09rc =3D brute_get_xattr_stats(dentry, inode, &stats); > +=09if (WARN_ON_ONCE(rc && rc !=3D -ENODATA)) > +=09=09goto unlock; I think I caught a problem here. Have you tested this with initramfs? According to init/do_mount.c's init_rootfs()/rootfs_init_fs_context(), when `root=3D` cmdline parameter is not empty, kernel creates rootfs of type ramfs (tmpfs otherwise). The thing about ramfs is that it doesn't support xattrs. I'm running this v8 on a regular PC with initramfs and having `root=3D` in cmdline, and Brute doesn't allow the kernel to run any init processes (/init, /sbin/init, ...) with err =3D=3D -95 (-EOPNOTSUPP) -- I'm getting a WARNING: CPU: 0 PID: 173 at brute_task_execve+0x15d/0x200 Failed to execute /init (error -95) and so on (and a panic at the end). If I omit `root=3D` from cmdline, then the kernel runs init process just fine -- I guess because initramfs is then placed inside tmpfs with xattr support. As for me, this ramfs/tmpfs selection based on `root=3D` presence is ridiculous and I don't see or know any reasons behind that. But that's another story, and ramfs might be not the only one system without xattr support. I think Brute should have a fallback here, e.g. it could simply ignore files from xattr-incapable filesystems instead of such WARNING splats and stuff. > + > +=09if (rc =3D=3D -ENODATA && bprm->secureexec) { > +=09=09brute_reset_stats(&stats); > +=09=09rc =3D brute_set_xattr_stats(dentry, inode, &stats); > +=09=09if (WARN_ON_ONCE(rc)) > +=09=09=09goto unlock; > +=09} > + > +=09rc =3D 0; > +unlock: > +=09inode_unlock(inode); > +=09return rc; > +} > + > > Thanks, Al