目次: 車
先週、ディーラーに半年点検に持っていったら毎度おなじみのバッテリーがイカレていて、日帰りの予定が1週間の入院と相成りました。バッテリー液の比重が1.2くらいになっていたみたいです(本来の比重は1.28)。今度こそバッテリー交換を提案されるかな?と思いきや「充電してみます!」とのことでした。
今回も何とかバッテリー君は復活してくれたようで良かったです。次はもうダメそうな雰囲気を感じましたけど……これまでのバッテリーに比べたら割と長期間耐えているのでまあ良いでしょう。
バッテリーが早々にイカレてしまう原因は、社外セキュリティの待機電力がでかいことと、あまりにも乗らなさすぎなことで、大阪に住んでいた10年以上前から原因は明確なんですが、ライフスタイルはそんな劇的に変えられないのです。
目次: Linux
前回はsystemd --userの/proc/[pid]/ioが読めない件を紹介しました。今回はなぜ読めないのか、Linuxカーネルのコードを見て謎を追います。
// fs/proc/base.c
/*
* Tasks
*/
static const struct pid_entry tid_base_stuff[] = {
DIR("fd", S_IRUSR|S_IXUSR, proc_fd_inode_operations, proc_fd_operations),
DIR("fdinfo", S_IRUGO|S_IXUGO, proc_fdinfo_inode_operations, proc_fdinfo_operations),
//...
#ifdef CONFIG_TASK_IO_ACCOUNTING
ONE("io", S_IRUSR, proc_tid_io_accounting), //★★★★
#endif
static int proc_tid_io_accounting(struct seq_file *m, struct pid_namespace *ns,
struct pid *pid, struct task_struct *task)
{
return do_io_accounting(task, m, 0); //★★★★
}
// fs/proc/base.c
#ifdef CONFIG_TASK_IO_ACCOUNTING
static int do_io_accounting(struct task_struct *task, struct seq_file *m, int whole)
{
struct task_io_accounting acct;
int result;
result = down_read_killable(&task->signal->exec_update_lock);
if (result)
return result;
if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) { //★★これ★★
result = -EACCES;
goto out_unlock;
}
if (whole) {
struct signal_struct *sig = task->signal;
struct task_struct *t;
unsigned int seq = 1;
unsigned long flags;
rcu_read_lock();
do {
seq++; /* 2 on the 1st/lockless path, otherwise odd */
flags = read_seqbegin_or_lock_irqsave(&sig->stats_lock, &seq);
acct = sig->ioac;
__for_each_thread(sig, t)
task_io_accounting_add(&acct, &t->ioac);
} while (need_seqretry(&sig->stats_lock, seq));
done_seqretry_irqrestore(&sig->stats_lock, seq, flags);
rcu_read_unlock();
} else {
acct = task->ioac;
}
seq_printf(m,
"rchar: %llu\n"
"wchar: %llu\n"
"syscr: %llu\n"
"syscw: %llu\n"
"read_bytes: %llu\n"
"write_bytes: %llu\n"
"cancelled_write_bytes: %llu\n",
(unsigned long long)acct.rchar,
(unsigned long long)acct.wchar,
(unsigned long long)acct.syscr,
(unsigned long long)acct.syscw,
(unsigned long long)acct.read_bytes,
(unsigned long long)acct.write_bytes,
(unsigned long long)acct.cancelled_write_bytes);
result = 0;
out_unlock:
up_read(&task->signal->exec_update_lock);
return result;
}
// kernel/ptrace.c
bool ptrace_may_access(struct task_struct *task, unsigned int mode)
{
int err;
task_lock(task);
err = __ptrace_may_access(task, mode); //★★これ★★
task_unlock(task);
return !err;
}
/* Returns 0 on success, -errno on denial. */
static int __ptrace_may_access(struct task_struct *task, unsigned int mode)
{
const struct cred *cred = current_cred(), *tcred;
struct mm_struct *mm;
kuid_t caller_uid;
kgid_t caller_gid;
if (!(mode & PTRACE_MODE_FSCREDS) == !(mode & PTRACE_MODE_REALCREDS)) {
WARN(1, "denying ptrace access check without PTRACE_MODE_*CREDS\n");
return -EPERM;
}
/* May we inspect the given task?
* This check is used both for attaching with ptrace
* and for allowing access to sensitive information in /proc.
*
* ptrace_attach denies several cases that /proc allows
* because setting up the necessary parent/child relationship
* or halting the specified task is impossible.
*/
/* Don't let security modules deny introspection */
if (same_thread_group(task, current))
return 0;
rcu_read_lock();
if (mode & PTRACE_MODE_FSCREDS) {
caller_uid = cred->fsuid;
caller_gid = cred->fsgid;
} else {
/*
* Using the euid would make more sense here, but something
* in userland might rely on the old behavior, and this
* shouldn't be a security problem since
* PTRACE_MODE_REALCREDS implies that the caller explicitly
* used a syscall that requests access to another process
* (and not a filesystem syscall to procfs).
*/
caller_uid = cred->uid;
caller_gid = cred->gid;
}
tcred = __task_cred(task);
if (uid_eq(caller_uid, tcred->euid) &&
uid_eq(caller_uid, tcred->suid) &&
uid_eq(caller_uid, tcred->uid) &&
gid_eq(caller_gid, tcred->egid) &&
gid_eq(caller_gid, tcred->sgid) &&
gid_eq(caller_gid, tcred->gid))
goto ok;
if (ptrace_has_cap(tcred->user_ns, mode))
goto ok;
rcu_read_unlock();
return -EPERM;
ok:
rcu_read_unlock();
/*
* If a task drops privileges and becomes nondumpable (through a syscall
* like setresuid()) while we are trying to access it, we must ensure
* that the dumpability is read after the credentials; otherwise,
* we may be able to attach to a task that we shouldn't be able to
* attach to (as if the task had dropped privileges without becoming
* nondumpable).
* Pairs with a write barrier in commit_creds().
*/
smp_rmb();
mm = task->mm;
if (mm &&
((get_dumpable(mm) != SUID_DUMP_USER) &&
!ptrace_has_cap(mm->user_ns, mode)))
return -EPERM;
return security_ptrace_access_check(task, mode); //★★これ★★
}
//security/security.c
int security_ptrace_access_check(struct task_struct *child, unsigned int mode)
{
return call_int_hook(ptrace_access_check, child, mode);
}
// include/linux/ptrace.h
//★★modeに指定されているPTRACE_MODE_READ_FSCREDS = MODE_READ | MODE_FSCREDSのこと
#define PTRACE_MODE_READ 0x01
#define PTRACE_MODE_ATTACH 0x02
#define PTRACE_MODE_NOAUDIT 0x04
#define PTRACE_MODE_FSCREDS 0x08
#define PTRACE_MODE_REALCREDS 0x10
/* shorthands for READ/ATTACH and FSCREDS/REALCREDS combinations */
#define PTRACE_MODE_READ_FSCREDS (PTRACE_MODE_READ | PTRACE_MODE_FSCREDS)
#define PTRACE_MODE_READ_REALCREDS (PTRACE_MODE_READ | PTRACE_MODE_REALCREDS)
#define PTRACE_MODE_ATTACH_FSCREDS (PTRACE_MODE_ATTACH | PTRACE_MODE_FSCREDS)
#define PTRACE_MODE_ATTACH_REALCREDS (PTRACE_MODE_ATTACH | PTRACE_MODE_REALCREDS)
まずはここまでです。それでも結構長いですね。呼び出しの順序はこんな感じです。
proc_tid_io_accounting() do_io_acconting() ptrace_may_access() security_ptrace_access_check() call_int_hook(ptrace_access_check)
最後のcall_int_hook()の後はセキュリティ機能(SELinuxとか)の有無によって呼び出される関数が変わるようですが、一番大本のコンフィグCONFIG_SECURITYが有効になっていると、ケーパビリティのチェックが実行されます。
// include/linux/capability.h
/*
* Check if "a" is a subset of "set".
* return true if ALL of the capabilities in "a" are also in "set"
* cap_issubset(0101, 1111) will return true
* return false if ANY of the capabilities in "a" are not in "set"
* cap_issubset(1111, 0101) will return false
*/
static inline bool cap_issubset(const kernel_cap_t a, const kernel_cap_t set)
return !(a.val & ~set.val);
// security/commoncap.c
int cap_ptrace_access_check(struct task_struct *child, unsigned int mode)
{
int ret = 0;
const struct cred *cred, *child_cred;
const kernel_cap_t *caller_caps;
rcu_read_lock();
cred = current_cred();
child_cred = __task_cred(child);
if (mode & PTRACE_MODE_FSCREDS)
caller_caps = &cred->cap_effective; //★こちらを通る★
else
caller_caps = &cred->cap_permitted;
if (cred->user_ns == child_cred->user_ns &&
cap_issubset(child_cred->cap_permitted, *caller_caps)) //★★★★この条件を満たさない
goto out;
if (ns_capable(child_cred->user_ns, CAP_SYS_PTRACE))
goto out;
ret = -EPERM; //★★★★EPERMエラーが返される
out:
rcu_read_unlock();
return ret;
}
相手側(child, systemd --userのプロセス)のeffectiveなケーパビリティが、自分側(child, ユーザーが起動したcatとか)のケーパビリティのサブセットかどうか?を見る部分でエラーになります。
プロセスのケーパビリティを確認するには/proc/[pid]/statusを見ます。effectiveなケーパビリティはCapEffに表示されます。通常のプロセスは0ですが、systemd --userはCapEffに0ではない値が入っています。
$ cat /proc/1381/status | grep Cap CapInh: 0000000800000000 CapPrm: 0000000800000000 CapEff: 0000000800000000 CapBnd: 000001ffffffffff CapAmb: 0000000000000000
ケーパビリティを見やすく表示してくれるツールgetpcaps(Debianパッケージ名はlibcap2-bin)で見ると、
$ sudo getpcaps 1381 1381: cap_wake_alarm=eip
相手側のsystemd --userはcap_wake_alarmケーパビリティを持っていて、catなどは持っていません。従ってsystemd --userのケーパビリティはcatのケーパビリティのサブセット「ではない」です。先ほど紹介したコードにあったif文の条件を満たせずにEPERMが返されます。
目次: Linux
LinuxのI/O統計情報(/proc/[pid]/io)はユーザーが自分と一致するプロセスなら基本的に読めるはずですが、1つだけ読めない変なプロセスがいることに気づきました。
$ LANG=C ls -l /proc/*/io | grep $USER | cut -d ' ' -f 9 | xargs cat > /dev/null cat: /proc/1381/io: 許可がありません cat: /proc/1744137/io: そのようなファイルやディレクトリはありません cat: /proc/1744138/io: そのようなファイルやディレクトリはありません cat: /proc/1744139/io: そのようなファイルやディレクトリはありません $ ps 1381 PID TTY STAT TIME COMMAND 1381 ? Ss 0:00 /usr/lib/systemd/systemd --user $ ps u 1381 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND katsuhi+ 1381 0.0 0.0 21512 11360 ? Ss 2024 0:00 /usr/lib/syst $ /usr/lib/systemd/systemd --version systemd 257 (257~rc3-1) +PAM +AUDIT +SELINUX +APPARMOR +IMA +IPE +SMACK +SECCOMP +GCRYPT -GNUTLS +OPENSSL +ACL +BLKID +CURL +ELFUTILS +FIDO2 +IDN2 -IDN +IPTC +KMOD +LIBCRYPTSETUP +LIBCRYPTSETUP_PLUGINS +LIBFDISK +PCRE2 +PWQUALITY +P11KIT +QRENCODE +TPM2 +BZIP2 +LZ4 +XZ +ZLIB +ZSTD +BPF_FRAMEWORK +BTF -XKBCOMMON -UTMP +SYSVINIT +LIBARCHIVE
プロセスID 1744137〜1744139は、/proc/[pid]/ioの列挙に使ったls, grep, cutが、xargs catを実行するときに終了しているためで、気にしなくて良いはずです。しかしプロセスID 1381のsystemd --userは何かおかしいです。自分のプロセスのはずなのに読み出せなくてEPERMが返ります。
不思議ですね。ファイルを読み出せるかどうかチェックするaccess()を使って調べましょう。
#include <stdio.h>
#include <unistd.h>
void usage(int argc, char *argv[])
{
printf("usage:\n"
" %s filepath [filepath2 ...]\n", argv[0]);
}
const char *is_ok(int v)
{
if (v == 0) {
return "ok";
} else {
return "ng";
}
}
int main(int argc, char *argv[])
{
if (argc < 2) {
usage(argc, argv);
return 1;
}
for (int i = 1; i < argc; i++) {
const char *path = argv[i];
int fok, rok, wok, xok;
fok = access(path, F_OK);
rok = access(path, R_OK);
wok = access(path, W_OK);
xok = access(path, X_OK);
printf("%20s: f:%s r:%s w:%s x:%s\n",
path, is_ok(fok), is_ok(rok), is_ok(wok), is_ok(xok));
}
return 0;
}
$ gcc -O2 main.c -o access_check $ ./access_check /proc/1381/io /proc/1381/io: f:ok r:ok w:ng x:ng $ cat /proc/1381/io cat: /proc/1381/io: 許可がありません
ファイルが存在し(f:ok)、読み出しが可能である(r:ok)と判定されますが、catで読もうとするとやはりEPERMが返されます。なんだこれは……?
目次: Linux
Linuxは各プロセスがどれくらいI/Oを行ったか記録していて、procファイルシステムの/proc/[pid]/ioファイルから読み出すことができます。
各フィールドの意味についてはUbuntuのマニュアル(Ubuntu Manpage: proc - プロセスの情報を含む疑似ファイルシステム)が日本語でも読めるしわかりやすいです。
(catを起動する、pidは1690787) $ cat /proc/1690787/io rchar: 3980 wchar: 0 syscr: 9 syscw: 0 read_bytes: 0 write_bytes: 0 cancelled_write_bytes: 0 (catにaとEnterを入力する) $ cat /proc/1690787/io rchar: 3982 wchar: 2 syscr: 10 syscw: 1 read_bytes: 0 write_bytes: 0 cancelled_write_bytes: 0
例としてaとEnterをcatに入力してみました。読み出し側を見てみると、rcharが2増えているのでaと改行文字の2バイトを、syscrが1増えているので1回のread()システムコールで読み出しているのでしょう。read_bytesが増えていないところを見ると、ファイルではなく端末から読み出したことも推測できます。
書き込み側はwcharが2増えているのでaと改行文字の2バイトを、syscwが1増えているので1回のwrite()システムコールで端末に書き出したと推測できます。読み込み側と異なり、書き込み側はファイルシステム層に書き出したかどうかは不明な仕様です。
最近のUbuntuやDebianであればデフォルト有効ですが、わざと無効にしているシステムもあるので有効にする方法を紹介しておきます。
LinuxカーネルのCONFIG_TASK_IO_ACCOUNTINGを有効にすると使用できます。CONFIG_TASK_XACCT、CONFIG_TASKSTATSに依存しているので合わせて有効にする必要があります。menuconfigから有効にする場合は下記の場所にあります。
General setup ---> CPU/Task time and stats accounting ---> [*] Export task/process statistics through netlink [*] Enable extended accounting over taskstats [*] Enable per-task storage I/O accounting
ちなみにx86_64向けではデフォルトコンフィグarch/x86/configs/x86_64_defconfigで太古の昔、2008年くらい(2.6.30くらいの時代)から有効になっています。
< | 2025 | > | ||||
<< | < | 03 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | - | - | - | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | - | - | - | - | - |
合計:
本日: