Not only is the Internet dead, it's starting to smell really bad.:2015年01月下旬

2015/01/21(Wed)

[NetBSD] netbsd-5枝はgethostent_r(3)が壊れている

前回ちょっと触れたワイのNIS/NFS環境ですが、NISは passwd だけでなく hosts も配れるので、いちいち DNS を建てる必要がないのもメリットです。 欠点は DNS みたいに DHCP が NIS サーバの IP アドレスをクライアントに知らせることはできませんが、そもそも NIS の ypbind は broadcast オプションで勝手にサーバ探す設定もできるしね。 まぁ broadcast オプション付けなくても /etc/hosts に NIS サーバだけ書くのはそんな手間でないし。

@ところが /etc/hosts でホスト名解決できないんですがンゴゴゴゴ

ところがですね netbsd-5 枝つまり 5.2-STABLE の /etc/hosts に NISサーバのエントリ追加してもホスト名解決できないんですわ、なんぞこれ…

# grep unko /etc/hosts
192.0.2.128 unko
# ping unko
ping: Cannot resolve "unko" (Unknown host)

ちなみに netbsd-5-2 枝 つまり 5.2.3-STABLE では問題ないもよう、うーんこの。

ここ最近 ntp とか bind の脆弱性もあったしなーと思いつつ netbsd-5 枝への pullup リクエスト眺めてたけど、そいつらではなく このPRがくさそう *1

@gethostent(3) と gethostent_r(3) の実装を読んでみる。

元々 4.2BSD の getservent(3) の頃からつい最近まで /etc/hosts を読むのは fgets(3)を使う実装やね、 ソース

...
again:
	if ((p = fgets(hostbuf, BUFSIZ, hostf)) == NULL)
		return (NULL);
	if (*p == '#')
		goto again;
	cp = strpbrk(p, "#\n");
	if (cp == NULL)
		goto again;
	*cp = '\0';
	cp = strpbrk(p, " \t");
	if (cp == NULL)
		goto again;
	*cp++ = '\0';
...

ところが fgets(3) だとこのコードの場合 BUFSIZ を超えると行がちょん切れますので、fgetln(3) を使うように、この commitでこっそり変更されています。つーか本来このコミットの目的であるMT-Safe版APIの追加作業に無関係の修正が混じるのはアレなんですが、滝川クリストスさんなので察しろ。

 again:
	if ((p = fgetln(hf, &llen)) == NULL) {
		*he = HOST_NOT_FOUND;
		return NULL;
	}
	if (llen < 1)
		goto again;
	if (*p == '#')
		goto again;
	p[llen] = '\0';
	if (!(cp = strpbrk(p, "#\n")))
		goto again;
	*cp = '\0';
	if (!(cp = strpbrk(p, " \t")))
		goto again;
	*cp++ = '\0';

ちなみに fgetln(3) は fgets(3) と違い一行分のバッファを要求しないので

  • もしかして動的メモリ割り当て?
  • もしかして非スレッドセーフ?

を疑ってしまいますが、これ FILE 構造体の中にバッファを確保して使いまわしてます。よって開放する必要もないし複数のスレッドでFILEを共有しない限りスレッドセーフです。

そんでですね fgetln(3) で返される1行のバッファですが、これは '\0'つまり NUL 文字で終端処理されません、これはマニュアルの警告(CAVEATS)セクションにも書いてあります。

CAVEATS
     Since the returned buffer is not a C string (it is not null terminated),
     a common practice is to replace the newline character with `\0'.
     However, if the last line in a file does not contain a newline, the
     returned text won't contain a newline either.  The following code
     demonstrates how to deal with this problem by allocating a temporary
     buffer:
             char *buf, *lbuf;
             size_t len;

             lbuf = NULL;
             while ((buf = fgetln(fp, &len))) {
                     if (buf[len - 1] == '\n')
                             buf[len - 1] = '\0';
                     else {
                             if ((lbuf = (char *)malloc(len + 1)) == NULL)
                                     err(1, NULL);
                             memcpy(lbuf, buf, len);
                             lbuf[len] = '\0';
                             buf = lbuf;
                     }
                     printf("%s\n", buf);

                     if (lbuf != NULL) {
                             free(lbuf);
                             lbuf = NULL;
                     }
             }

行が '\n' つまり改行コードで終わってるならそこを

                             buf[len - 1] = '\0';

として終端すりゃいいんですが、そうでければ

                             if ((lbuf = (char *)malloc(len + 1)) == NULL)
                                     err(1, NULL);
                             memcpy(lbuf, buf, len);
                             lbuf[len] = '\0';
                             buf = lbuf;

のように返された長さをプラス 1byte してあげた上で複製を作らないと

  • buf[len - 1] は有効な文字が入ってるのでホスト名やエイリアスの最後1文字が欠ける可能性
  • buf[len] に書き込んだらそれは即バッファオーバーフローの可能性(実装次第)

ちゅーことになるわけです。オー、バッドデザイン!

@そもそも fgetln(3) の使い方間違ってるやんけ

ところがさっきの書き換えられた gethostent_r(3) のコードですが

	p[llen] = '\0';

と、常に書込み禁止であるべき場所に終端文字書き込んでやがるので、バッファオーバーフローを起こす可能性ですやん。きっと全くマニュアル読んでないゾ~これ(通常運転)。

ところがこのgethostent_rのクソコードそのものが問題になってるわけではないんですね、同じ pullup が netbsd-6 枝にも入ってるんですがそっちはちゃんと/etc/hostsで名前解決できてるんですな。 つーことでもういっこ別の問題があると思われるわけです。

@次回予告

ちゅーことで fgetln(3) のソースを読んで、真の原因を突き止めるまでをお届けする予定、最近文章書く気力がめっきりポッキリなのでまた書かずに終わる可能性もある *2

*1:GNHくんはくさくないし、くさそうでもないだろいい加減にしろ!
*2:もう手元では解決済だし、俺は嫌な思いしていないからの精神。

2015/01/22(Thu)

[NetBSD] netbsd-5枝はgethostent_r(3)が壊れている(その2)

@やっぱり壊れてるじゃないか(憤怒)

前回 gethostent_r(3) が

   い  つ  も  の
    確 定 演 出
実 家 の よ う な 安 心 感

通りにぶっ壊れてるちゅー話を書きました、また か壊れるなぁ。

@そもそも fgetln(3) って何よ

前回 fgetln(3) がクソ使いづらい話を書きましたが、そもそもこいつは 4.4BSD 由来の関数で移植性がないんですわ、 何これも残当。

たぶん滝川先生は この辺読んだりして fgets(3) から fgetln(3) への書き換えに思い至ったのだろう、ナムナム。

ちな POSIX には別に getdelim(3) および getline(3) ちゅー関数があるので移植性を考えたら場合そちらを使うべきです *1

ssize_t getdelim(char **restrict lineptr, size_t *restrict n,
       int delimiter, FILE *restrict stream);
ssize_t getline(char **restrict lineptr, size_t *restrict n,
       FILE *restrict stream);

…とはいいつつ、この関数って元々 glibc2 の拡張が POSIX に突っ込まれたものであり *BSD で実装されだしたの結構最近なんですな。 NetBSDだと 5.99 で実装されて netbsd-5 枝にバックポートはされていませんので netbsd-6 以降でしか使えない問題があります。

@ん?今 netbsd-6 っていったよね?

前回 /etc/hosts で名前解決できない現象は netbsd-5 枝のみで、同じプルリクが入った netbsd-6 には影響が無いので別の問題も関係してるんじゃないか?と書いたのですが。 うーむ netbsd-6 に getline(3) の実装が追加されたっての、あやしい…あやしくない?

@ソース読んでみる

ということで netbsd-6 の fgetln(3) のコード、実体は fgetstr.c なんですが、履歴を追うと この commitで getdelim(3) のスレッドアンセーフ版である __getdelim() を呼ぶように変更されてます。

最新の __fgetstr() の コード をみると

/*
 * Get an input line.
 * This now uses getdelim(3) for a code reduction.
 * The upside is that strings are now always NULL terminated, but relying
 * on this is non portable - better to use the POSIX getdelim(3) function.
 */
char *
__fgetstr(FILE *__restrict fp, size_t *__restrict lenp, int sep)
{
	ssize_t n;

	_DIAGASSERT(fp != NULL);
	_DIAGASSERT(lenp != NULL);

	n = __getdelim(&_EXT(fp)->_fgetstr_buf, &_EXT(fp)->_fgetstr_len, sep, fp);
	if (n == -1) {
		*lenp = 0;
		if (__sferror(fp) && errno == EOVERFLOW) /* fixup errno */
			errno = EINVAL;
		return NULL;
	}
	*lenp = n;
	return _EXT(fp)->_fgetstr_buf;
}

ちゅーように、専用のバッファ( struct __sfileext の _fgetstr_{len, buf}) が用意されています。

そして getdelim(3) の仕様をもう一度思い出してみましょう。

~ including room for the delimiter and a terminating NUL.
~ and a terminating NUL added when the delimiter or end of file is encountered.

ちゅーことで必ず最後は'\0'で終端処理されます。ですので

  • 本来 fgetln(3) は終端処理をしないにもかかわらず
  • 内部で getdelim(3) が動いて終端処理を実施するので、実際に必要な長さ +1 でメモリは確保されている

という動作になってるんですな。

なので前回の

	p[llen] = '\0';

ちゅークソコード、仕様としてはバッファオーバーランでアウトだけども、実装的にはどこのメモリも破壊しないので動いてるちゅーこと。

@netbsd-5 での fgetln(3) の実装は?

内部 __getdelim() 化される前の __fgetstr() は こんなん

    117 		ret = (char *)fp->_p;
    118 		*lenp = len = p - fp->_p;
    119 		fp->_flags |= __SMOD;
    120 		fp->_r -= len;
    121 		fp->_p = p;
    122 		return (ret);

...

    165 	*lenp = len;
    166 #ifdef notdef
    167 	fp->_lb._base[len] = 0;
    168 #endif
    169 	return ((char *)fp->_lb._base);
    170

つまりは fgetc(3) 用のバッファである struct __sFILE の _{r,p}

    106 	unsigned char *_p;	/* current position in (some) buffer */
    107 	int	_r;		/* read space left for getc() */

あるいは fgetln(3) 用のバッファである struct __sFILE の _lb

     73 /* stdio buffers */
     74 struct __sbuf {
     75 	unsigned char *_base;
     76 	int	_size;
     77 };
...
    132 	/* separate buffer for fgetln() when line crosses buffer boundary */
    133 	struct	__sbuf _lb;	/* buffer for fgetln() */

を使ってるのですが、後者はまだしも前者に対してさっきの

	p[llen] = '\0';

のような破壊活動が行われると、次に読み込むべき行の先頭が'\0'で終端されてしまうわけです。本来

"192.0.2.1	unko\n"

という内容の行が返されるところ先頭が'\0'で潰されて

"\092.0.2.1	unko\n"

となってるわけです、よって最初の行より後は str* 系の関数から見たらこれ全て空行にしか見えないわけ。そら /etc/hosts で名前解決できるわきゃないわー。

このコードを netbsd-5 で実行すると2行目以降はなんも表示されないはずよ。

$ cat >unko.c
#include <stdio.h>
#include <string.h>

int
main(void)
{
	FILE *fp;
	char *buf;
	size_t len;

	fp = fopen("/etc/hosts", "r");
	while ((buf = fgetln(fp, &len)) != NULL) {
		buf[len] = '\0';
		len = strlen(buf);
		printf("%zd - [%s]\n", len, buf);
	}
	return 0;
}
^D
$ make unko
$ ./unko
53 - [# $NetBSD: hosts, v1.7 2004/08/29 13:26:17 chs Exp $
]
0 - []
0 - []
0 - []
0 - []
...

ほらね。

@結論

実はですね、この問題って既知のもので netbsd-5枝に 修正依頼はとっくの昔にでてるんですな。しかし releng チーム は netbsd-5 枝への独自パッチは拒否し、HEAD からのマージじゃないと壊れて動かなかろうが7ヶ月放置するのが方針みたいね。なんだかなぁ *2

ちなみに HEAD では fgetln(3) から更に別の fparseln(3) というこれまた独自関数に 置換されてしまった後なので、また修正が大掛かりになるわけですが。

そもそも HEADからのマージ以外受付けないって枝の存在意義よーわからんしそもそも最初にマージする前にテストって以下略。まぁ security チームや releng チームが修正の中身なんも検証してないのは CVE-2014-3951の一件でよーく存じておりまあばばばばば。

*1:まーこれ一行分のバッファ lineptr は malloc されるのでオーバーヘッドあるし free しないとメモリリークするし、ENOMEMケースのエラー処理も必要になるしで単純に置換はできないけどね。
*2:とはいえこのプルリクの修正も fgetln(3) の使い方間違ってるけどねw

2015/01/24(Sat)

[NetBSD] netbsd-5枝はgethostent_r(3)が壊れている(最終回)

さてワイの fork のベースになってる netbsd-6 枝は前回の説明のとおり fgetln(3) の中身が getdelim(3) なので、いくら gethostent_r(3) の中で off by one ワンワン やらかしても問題は起きないのですが、さすがに放置するのも嫌なので修正しましょう。

@まずは方針

修正のやり方としては

  • コードの差分を最小にするために、あくまで fgetln(3) の間違った使い方だけを修正する
  • 差分の量は一切気にせずに HEAD の実装をバックポートする

があってどっちをとるかなんですが、前者をやろうとするとそもそも fgetln(3) 使うと前回説明した通り、改行の無い最終行を正しく扱うためには 文字列の複製を作らなきゃならなくなる関係上、どうしてもコードが複雑になります。

簡潔に書くために strndup(3) みたいな POSIX:2008 の関数を使って行数を減らすよう努めても

--- gethnamaddr.c.orig	2015-01-24 15:51:43.880805400 +0900
+++ gethnamaddr.c	2015-01-24 16:04:33.699420400 +0900
@@ -733,18 +733,28 @@
 		errno = EINVAL;
 		return NULL;
 	}
+	goto loop;
+
  again:
+	free(buf)
+ loop:
 	if ((p = fgetln(hf, &llen)) == NULL) {
 		*he = HOST_NOT_FOUND;
 		return NULL;
 	}
 	if (llen < 1)
-		goto again;
+		goto loop;
 	if (*p == '#')
-		goto again;
-	p[llen] = '\0';
-	if (!(cp = strpbrk(p, "#\n")))
-		goto again;
+		goto loop;
+	if (p[llen - 1] == '\0')
+		--llen;
+	buf = strndup(p, llen);
+	if (buf == NULL) {
+		*he = NETDB_INTERNAL;
+		return NULL;
+	}
+	p = buf;
+	if ((cp = strpbrk(p, "#")) != NULL)
 	*cp = '\0';
 	if (!(cp = strpbrk(p, " \t")))
 		goto again;
@@ -754,8 +764,10 @@
 		len = NS_IN6ADDRSZ;
 	} else if (inet_pton(AF_INET, p, &host_addr) > 0) {
 		res_state res = __res_get_state();
-		if (res == NULL)
+		if (res == NULL) {
+			free(buf);
 			return NULL;
+		}
 		if (res->options & RES_USE_INET6) {
 			map_v4v6_address(buf, buf);
 			af = AF_INET6;
@@ -805,8 +817,10 @@
 	hent->h_aliases[anum] = NULL;
 
 	*he = NETDB_SUCCESS;
+	free(buf);
 	return hent;
 nospc:
+	free(buf);
 	errno = ENOSPC;
 	*he = NETDB_INTERNAL;
 	return NULL;

無駄に大きな差分やね、それと goto againする箇所に全部 free(buf) 入れるの嫌なので goto again に重ねて更に goto loop なんぞ追加して見苦しい *1

ということで fgetln(3) 捨てよう(迫真)。

@HEAD の gethostent_r(3) 実装をバックポートする

そもそも HEAD で確実にこの問題が直ってるかを確認しないと、また Broken by MFC(Merge From Current) をやらかして「おいゴルァ!」怒声が飛んでクルルァの後について事務所に監禁待った無しです、ソースを見てみましょう。

	for (;;) {
		free(p);
		p = fparseln(hf, NULL, NULL, NULL, FPARSELN_UNESCALL);
		if (p == NULL) {
			free(aliases);
			*he = HOST_NOT_FOUND;
			return NULL;
		}
		if (!(cp = strpbrk(p, " \t")))
			continue;
		*cp++ = '\0';

まーた新しい関数が出てきました、 fparseln(3)を使うように書き直されてます。これは NetBSD 独自の関数です。

SYNOPSIS
     #include <stdio.h>

     char *
     fparseln(FILE *stream, size_t *len, size_t *lineno, const char delim[3],
         int flags);

HISTORY
     The fparseln() function first appeared in NetBSD 1.4.

普段のワイだと移植性の無い関数を使うとdissりだしますが、fparseln(3) についてはもっと流行らせ!流行らせコラ!(ステマ) なんだなぁ。

@fparseln(3) を使うことのメリット

この関数は fgets(3)、fgetln(3)、getline(3)などこれまで出てきた関数と同様に「ファイルから1行読み込む」のは同じですが、それに加えて

  • delim[1]で指定したエスケープ文字が改行コードの直前で現れたら、継続行として扱う
  • delim[2]で指定したコメント文字が現れたら、そこから改行コードまでを無視する
  • 上記のエスケープ文字とコメント文字は、delim[0]で指定した文字でエスケープ可能

という機能が追加されています。これ yacc/lex 持ち出すまでも無い文法ってレベルじゃねぇ設定ファイルだけど、地味に実装がめんどくさいパース処理を書くのに大変便利な関数です。

ですので、netbsd-7以降では /etc/hosts において

192.0.2.1	unko \
			unko.example.com

192.0.2.1	unko unko.example.com

と等価で、改行をエスケープして継続行扱いで書けるようになって読み易く書けるようになってます。

また実装においても fgetln(3) などではコメント行の概念が無いので、呼び出し側においてコメント行か否かをチェックしていました、再掲。

 again:
	if ((p = fgetln(hf, &llen)) == NULL) {
		*he = HOST_NOT_FOUND;
		return NULL;
	}
	if (llen < 1)
		goto again;
	if (*p == '#')
		goto again;
	p[llen] = '\0';
	if (!(cp = strpbrk(p, "#\n")))
		goto again;
	*cp = '\0';

このコードが fparseln(3) 化することでガッツリ不要になるわけですな、よしこの変更をマージしよう!

@ところが差分はそのままは適用できないんだナ

そうは問屋が卸さない、この fparseln(3) 化は rev1.91 で行われたんですが、この差分はそのまま netbsd-6 枝に適用することができません。

というのもその前に rev1.86において、いっこのIPアドレスに対して指定できるホスト名とそのエイリアスの実装上の上限、MAXALIASES(=35)を超えて書けるようにする変更が入ってるんですな。 そんでこれは netbsd-5 や netbsd-6 枝にはマージされてません。

そしてこの MAXALIASES 定数は gethostent_r(3) 以外でも使われてます。

$ pwd
/usr/src/lib/libc/net
$ grep -r MAXALIASES .
./getnetent.c:#define   MAXALIASES      35
./getnetent.c:static char *net_aliases[MAXALIASES];
./getnetent.c:                  if (q < &net_aliases[MAXALIASES - 1])
./getnetnamadr.c:#define        MAXALIASES      35
./getnetnamadr.c:static char *net_aliases[MAXALIASES];
./getnetnamadr.c:                       if (q < &net_aliases[MAXALIASES - 1])
./hostent.h:#define     MAXALIASES      35
./sethostent.c: char *aliases[MAXALIASES];

このうち getnetent.c と getnetnamadr.c に関しては hosts(5) ではなく networks(5) の操作なのでおそらく問題ないと思われますが、sethostent.c にある sethostent_r(3) は gethostent_r(3) で上限を撤廃したことで、オーバーランないしアンダーランによる悪影響が起こりえないかかを確認しないとまずいです。

ところが 最新のコードみてもまだ sethostent.c には MAXALIASES 制限あるのよね

153 struct hostent *
154 _hf_gethtbyname2(const char *name, int af, struct getnamaddr *info)
155 {
...
160 	char *aliases[MAXALIASES];
...
187 		hp = gethostent_r(hf, info->hp, info->buf, info->buflen,
188 		    info->he);
...
205 			for (anum = 0; hp->h_aliases[anum]; anum++) {
206 				if (anum >= __arraycount(aliases))
207 					goto nospc;
208 				HENT_SCOPY(aliases[anum], hp->h_aliases[anum],
209 				    ptr, len);
210 			}
...

ソース解説すると 187 行目で制限の無くなった gethostent_r(3) を呼出してホスト名とエイリアスの一覧を取得して、208 行目で MAXALIASES 制限のある固定長配列 aliases にコピーしてます。 まぁ 206 行目の anum >= __arraycount(aliases)) でMAXALIASESを超えたら errnoとしてENOSPCを返す処理にジャンプしてますので、バッファオーバーフローでクラッシュすることは無いね。 ちなみにこの _hf_gethtbyname2() は gethostbyname_r(3) から呼び出されます。

@昨日もまたバグ出しましたね?

ということで 今のNの実装では

  • gethostent_r(3) で一覧を取得する場合においては MAXALIASES 制限はない
  • gethostbyname_r(3) で名前解決を行おうとすると MAXALIASES 制限に引っかかって失敗する

うーんこの。

確かに動かないっぽい。

$ grep unko /etc/hosts
192.0.2.1       unko00 unko01 unko02 unko03 unko04 unko05 unko06 unko07 unko08 unko09 \
                unko10 unko11 unko12 unko13 unko14 unko15 unko16 unko17 unko18 unko19 \
                unko20 unko21 unko22 unko23 unko24 unko25 unko26 unko27 unko28 unko29 \
                unko30 unko31 unko32 unko33 unko34 unko35 unko36
$ ping unko36
ping: Cannot resolve "unko36" (Unknown host)

エイリアスを減らすと動く。

$ grep unko /etc/hosts
192.0.2.1       unko00 unko01 unko02 unko03 unko04 unko05 unko06 unko07 unko08 unko09 \
                unko10 unko11 unko12 unko13 unko14 unko15 unko16 unko17 unko18 unko19 \
                unko20 unko21 unko22 unko23 unko24 unko25 unko26 unko27 unko28 unko29 \
                unko30 unko31 unko32 unko33 unko34 unko35
$ ping unko35
PING unko00 (192.0.2.1): 48 data bytes

まぁ gethnameaddr.c の rev1.87~1.90 において off by one やら resource leak やらクソコード指摘されまくってるので、テストしてないとは思ってましたが。

とはいえこーゆー整合性の無い状態になってるってもうねアボガドバナナですよ、まぁ rev1.86 の commiter の名前みて納得、 また君か壊れるなぁ。

@結論

なんかもうめんどくさくなってきたので、ワイの N6 fork にはそのまま rev1.91 までの変更を分割して取り込んだですよ。

そんで sethostent.c における不整合に関してはまた改めて直すちゅーことで。

*1:そもそもこれでも自分メモリリークしない自信はないゾ。

2015/01/25(Sun)

[NetBSD] netbsd-5枝はgethostent_r(3)が壊れている(エピローグ)

ちゅーことでgethostbyname_r(3)のMAXALIASES制限撤廃が 壊れてる件についてはワイの fork では 直りました

ちなみに全くテストしてないので全て自己責任でご利用ください。

2015/01/28(Wed)

[NetBSD] 再び gethostbyname(3)のお話、そして getaddrinfo(3) の逆襲

なんか昨日の夜から CVE-2015-0235で IPv6 元年(17歳教並みの欺瞞)の今年になって今更 gethostbyname(3) がアツゥイ!状態ですが、ここ数日の記事はこの脆弱性とは全くの無関係のお話なのでお間違いの無きよう。

ところで前回 /etc/hosts のパースが fparsln(3) 化されたちゅー記事を書いたんですが、そもそも gethostbyname(3) ってのはもう obsolete であって getaddrinfo(3) の方を使えなんですな *1

とはいえ未だ IPv6 未対応なんてのはゴロゴロしとります、ここしばらくワイ NIS で遊んでたのですが N とか O の ypbind(8) って未だに IPv6 対応されてないのですよ。なもんで gethostbyname(3) のバグを踏んだちゅーこと。

そんで /etc/hosts は gethostbyname(3) も getaddrinfo(3) も読むのでコードは共通化されてるあるいは両方とも fparseln(3) 化してると思ってたんですが やってなかったね(ゲッソリ)。

ということで 対応してみました

そもそも論として、NISで/etc/hosts配るときに互換性考えると「エスケープ + 改行」は使えないので

char delim[3] = { 0, 0, '#' };

p = fparseln(fp, NULL, NULL, delim, FPARSELN_UNESCALL);
if (p == NULL)
...

として、エスケープは禁止しといた方が相互運用的にベターかもね。

まぁ自分が使うだけなので便利な方にしとこう(適当)。

*1:まぁこんなチラシの裏を読んでる人はだいたい この本読んで知ってる感がありますが