コグノスケ


link 未来から過去へ表示(*)  link 過去から未来へ表示

link もっと前
2025年3月1日 >>> 2025年2月16日
link もっと後

2025年3月1日

レガシィの半年点検(2025)

目次:

先週、ディーラーに半年点検に持っていったら毎度おなじみのバッテリーがイカレていて、日帰りの予定が1週間の入院と相成りました。バッテリー液の比重が1.2くらいになっていたみたいです(本来の比重は1.28)。今度こそバッテリー交換を提案されるかな?と思いきや「充電してみます!」とのことでした。

今回も何とかバッテリー君は復活してくれたようで良かったです。次はもうダメそうな雰囲気を感じましたけど……これまでのバッテリーに比べたら割と長期間耐えているのでまあ良いでしょう。

バッテリーが早々にイカレてしまう原因は、社外セキュリティの待機電力がでかいことと、あまりにも乗らなさすぎなことで、大阪に住んでいた10年以上前から原因は明確なんですが、ライフスタイルはそんな劇的に変えられないのです。

編集者:すずき(2025/03/12 00:21)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2025年2月19日

LinuxのI/O統計情報が読めないプロセスの謎を追う

目次: Linux

前回はsystemd --userの/proc/[pid]/ioが読めない件を紹介しました。今回はなぜ読めないのか、Linuxカーネルのコードを見て謎を追います。

アクセス違反判定されるまでの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ではない値が入っています。

systemd --userプロセスのケーパビリティ
$ cat /proc/1381/status | grep Cap

CapInh: 0000000800000000
CapPrm: 0000000800000000
CapEff: 0000000800000000
CapBnd: 000001ffffffffff
CapAmb: 0000000000000000

ケーパビリティを見やすく表示してくれるツールgetpcaps(Debianパッケージ名はlibcap2-bin)で見ると、

getpcapsでケーパビリティを表示
$ sudo getpcaps 1381

1381: cap_wake_alarm=eip

相手側のsystemd --userはcap_wake_alarmケーパビリティを持っていて、catなどは持っていません。従ってsystemd --userのケーパビリティはcatのケーパビリティのサブセット「ではない」です。先ほど紹介したコードにあったif文の条件を満たせずにEPERMが返されます。

編集者:すずき(2025/03/01 15:49)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2025年2月18日

LinuxのI/O統計情報が読めないプロセスが居る

目次: Linux

LinuxのI/O統計情報(/proc/[pid]/io)はユーザーが自分と一致するプロセスなら基本的に読めるはずですが、1つだけ読めない変なプロセスがいることに気づきました。

ユーザーが自分と一致するプロセスのI/O統計情報を読めるかチェック
$ 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()で調べる

不思議ですね。ファイルを読み出せるかどうかチェックするaccess()を使って調べましょう。

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;
}
access()でファイルにアクセス可能かチェックした結果
$ 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が返されます。なんだこれは……?

編集者:すずき(2025/02/25 01:12)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2025年2月17日

LinuxのI/O統計情報

目次: Linux

Linuxは各プロセスがどれくらいI/Oを行ったか記録していて、procファイルシステムの/proc/[pid]/ioファイルから読み出すことができます。

各フィールドの意味についてはUbuntuのマニュアル(Ubuntu Manpage: proc - プロセスの情報を含む疑似ファイルシステム)が日本語でも読めるしわかりやすいです。

I/O統計情報(/proc/[pid]/io)の例
(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()システムコールで端末に書き出したと推測できます。読み込み側と異なり、書き込み側はファイルシステム層に書き出したかどうかは不明な仕様です。

I/O統計情報を有効にする方法

最近のUbuntuやDebianであればデフォルト有効ですが、わざと無効にしているシステムもあるので有効にする方法を紹介しておきます。

LinuxカーネルのCONFIG_TASK_IO_ACCOUNTINGを有効にすると使用できます。CONFIG_TASK_XACCT、CONFIG_TASKSTATSに依存しているので合わせて有効にする必要があります。menuconfigから有効にする場合は下記の場所にあります。

TASK_IO_ACCOUNTINGの設定位置
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/02/23 00:29)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link もっと前
2025年3月1日 >>> 2025年2月16日
link もっと後

管理用メニュー

link 記事を新規作成

<2025>
<<<03>>>
------1
2345678
9101112131415
16171819202122
23242526272829
3031-----

最近のコメント5件

  • link 25年2月10日
    すずきさん (02/13 02:03)
    「解読しました。なるほど、exitの引数が...」
  • link 25年2月10日
    hdkさん (02/12 08:06)
    「あ、すみません、比較元に間違いがありまし...」
  • link 25年2月10日
    hdkさん (02/12 08:01)
    「なるほど、最後に%rdiを0にするのはこ...」
  • link 20年6月29日
    すずきさん (02/12 00:12)
    「お役に立ったようであれば幸いです。」
  • link 25年2月7日
    すずきさん (02/12 00:11)
    「なるほど。動きそうなのになんで動かないん...」

最近の記事3件

  • link 25年3月1日
    すずき (03/12 00:21)
    「[レガシィの半年点検(2025)] 目次: 車先週、ディーラーに半年点検に持っていったら毎度おなじみのバッテリーがイカレていて...」
  • link 23年5月15日
    すずき (03/12 00:18)
    「[車 - まとめリンク] 目次: 車三菱FTOの話。群馬県へのドライブ1群馬県へのドライブ2将来車を買い替えるとしたら?FTO...」
  • link 25年3月10日
    すずき (03/12 00:06)
    「[誕生日] 42歳になりました。昨年の日記(2024年3月10日の日記参照)を見ると、リモートワークの話をしていました。最近は...」
link もっとみる

こんてんつ

open/close wiki
open/close Linux JM
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 2021年
open/close 2022年
open/close 2023年
open/close 2024年
open/close 2025年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報

合計:  counter total
本日:  counter today

link About www.katsuster.net
RDFファイル RSS 1.0

最終更新: 03/12 00:21