sysctl

怖いもの見たさで、『ゼロからはじめるenchant.js入門』【公式ガイド】なんて本を手に 取ってみた。

enchantって魅惑のって意味が有るそうだ。って事は、直訳すると魅惑のJavascriptって 事で、こう訴えるのは、一度書くと、ブラウザーの有る所なら、どこでも動きまっせって 甘い言葉なんだ。勿論、流行りのスマホ(iPhoneはおろかアンドロドロイドな奴も含む) でもOKって事です。

言語屋の標準語はPythonだと思っていたら、ゲーム屋の標準語はJavascriptなんですなあ。 正確に言うと、舞台はHTML5の上のキャンバスです。ここで、アニメ(の画像)を動かしたり 音を鳴らしたり、写真にキャラクターを乗せた、ポケモンGoっぽい事も出来ます。

enchant.jpは、その為のライブりりぃー集だそうです。これを使えば、いとも簡単に 小学生でも、ゲームが作れるそうです。

下記は、本に載ってたリンクです。

enchant.js

jsdo.it Share JavaScript, HTML5 and CSS.

9leap 作品投稿サイト

少女と少年のための投稿型プログラミング情報ブログ

HTML quick ref

これを見ながら、やっていくとよいそうですよ。

Ruby on enchant.js / RubyでHTML5ゲームを作ってみる

JavaScriptを別な言語で書くためのライブラリまとめ

sysctlの(不思議な)挙動

前回、ddbを起動させようとして、手探りしてたんだ。で、分かった事は、カーネルを コンパイルする時、ddb.consoleが1になるようにオプションを与える事だった。

で、このオプションで、てっきり内部定数がセットされると思っていた。だったら、後から sysctlでは変更不可だろう。以後、このカーネルは何時でも、ddbを呼び出せる危ない奴に なってると思ったんだ。

そこで、そのSWを落としてみる。結果、SWはoffになった。元に戻そうとすると、 壊れたSWみたいに、もう戻らない(onにならない)

# sysctl ddb.console=0
ddb.console: 1 -> 0
# sysctl ddb.console=1
sysctl: ddb.console: Operation not permitted

この挙動、安全側に倒れるんだから、それでいいじゃんと言う見方も出来るけど、いまいち 納得出来ない。そう言えば、sysctlがどう動いているか、調べようとして、路頭に迷って しまっていたな。良い機会なんで、もう少し真面目に調べてみるかな。

予備調査

manをざっと見。カーネル変数(適切な字句が浮かばないので、こう呼んでおく)でも、 変更出来るものと出来ないものが有るって事。タイプと共に列挙されてた。

     Name                                 Type       Changeable
     kern.ostype                          string     no
     kern.osrelease                       string     no
      :
     ddb.console                          integer    yes
     ddb.log                              integer    yes
     ddb.trigger                          integer    yes
     vfs.mounts.*                         struct     no
      :

これを見ると、ddb.consoleはinteger型で変更可能って事だ。

続いてみて行くと、関連ファイルの所にddb関係者の名前が列挙されるとな。

     <ddb/db_var.h>            definitions for second level ddb identifiers

それを見ると、

#define DBCTL_CONSOLE   6
#define DBCTL_LOG       7

#define CTL_DDB_NAMES { \
        { NULL, 0 }, \
           :
        { "console", CTLTYPE_INT }, \
        { "log", CTLTYPE_INT }, \
        { "trigger", CTLTYPE_INT }, \
}

extern int      db_console;
extern int      db_log;

それっぽい定義が出てきたな。

カーネル内で定義されてる変数名は、db_console なんだろうと当たりを付けて、nm /bsdして 探してみると、

00000000 F db_command.c
ffffffff81163b40 T db_command_loop
ffffffff818b95c0 D db_command_table
ffffffff818bcb24 D db_console
ffffffff81166e80 T db_continue_cmd

ここから、いきなりカーネルのソースに行ってしまって良いものだろうか。まあ、参考に 眺めておくか。どうやら、勇み足だった。ヘッダーファイルを通じて、変数名が漏れ出して いるのだろう。真面目に探してみる。

[ob: ddb]$ grep db_console *
db_usrreq.c:                        &db_console));
db_usrreq.c:                    ctlval = db_console;
db_usrreq.c:                    db_console = ctlval;
db_usrreq.c:            if (newp && db_console) {
db_var.h:extern int     db_console;

クラスターが見つかったので、ざっと見。 金鉱発見しましただ。

int
ddb_sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp, void *newp,
    size_t newlen, struct proc *p)
{
          :
	case DBCTL_CONSOLE:
                if (securelevel > 0)
                        return (sysctl_int_lower(oldp, oldlenp, newp, newlen,
                            &db_console));
                else {
                        ctlval = db_console;
                        if ((error = sysctl_int(oldp, oldlenp, newp, newlen,
                            &ctlval)) || newp == NULL)
                                return (error);
                        if (ctlval != 1 && ctlval != 0)
                                return (EINVAL);
                        db_console = ctlval;
                        return (0);
                }
                break;
        case DBCTL_LOG:
                return (sysctl_int(oldp, oldlenp, newp, newlen, &db_log));

これを見ると、更にメインSWが有るな。kern.securelevel=1 この値、boot時に0から1 に変化してたな。

多分、sysctl_int_lowerって関数は、今設定されてるより小さい値への 変更しか許さないんだろう。はて、どこで定義されてる?

kern/kern_sysctl.cが文字通り、カーネル側の処理をまとめたものだな。システムコール っぽいな。

#ifdef DDB
        case CTL_DDB:
                fn = ddb_sysctl;
                break;
#endif

カーネルをコンパイルする時に指定したDDBってオプションで、この部分が有効になるとな。 このDDBってオプションは、Makefile中に、 -DDDBと言う形で埋め込まれていた。

その他、DEBUGってオプションも有って、自分の好きなdebuggerでカーネルを弄ぶ事も 出来るみたいだ。そこまで、やる人はいるのか分からんけど。

お目当てのコードが出てきたぞ。

sysctl_int_lower(void *oldp, size_t *oldlenp, void *newp, size_t newlen, int *v\
alp)
{
        unsigned int oval = *valp, val = *valp;
        int error;

         :
        if (val > oval)
                return (EPERM);         /* do not allow raising */
        *(unsigned int *)valp = val;
        return (0);
}

EPERMって何だ? そんな時は、man errno するに限る。

     1 EPERM Operation not permitted. An attempt was made to perform an
             operation limited to processes with appropriate privileges or to
             the owner of a file or other resources.

予備調査と思って始めたのに、いつの間にか本筋に達してしまったぞ。経験値が上がって きた証拠ですかね。

sysctlはシステムコールか?

上では、勝手にシステムコールと断定しちゃったけど、それって見込み逮捕じゃないですかって 言われそう。じゃ、ドキュメントがきちんとしてる、OpenBSDならでは調査をしろ。

man 2 sysctl したらヒットせず。man 3 sysctl したら、出てきた。オブラートで 包んであるのかな。ざっと見すると、

     DBCTL_CONSOLE
             When this variable is set, an architecture dependent magic key
             sequence on the console or a debugger button will permit entry
             into the kernel debugger.  When running with a securelevel(7)
             greater than 0, this variable may not be raised.

今、コードに当たって得意満面としてたのが、きっちり文書になってるじゃん。 がっくりしました。いや、補強出来ましたよ。

system callの一覧はmanには無いのかな? 増減が有るんで、manに反映させるのは 諦めたのかな。しょうがないので、/usr/include/sys/syscall.h を見ろって事 なんだろうな。なんせ、それが時価と言うか、一番フレッシュですからね。 ちょいと、裏を取ってみる。

/* syscall: "sysctl" ret: "int" args: "const int *" "u_int" "void *" "size_t *"
"void *" "size_t" */
#define SYS_sysctl      202

載ってて、良かった。これで確信を持てたぞ。

                                /* 326 is obsolete t32_utimensat */
                                /* 327 is obsolete t32_futimens */
                                /* 328 is obsolete __tfork51 */
/* syscall: "__set_tcb" ret: "void" args: "void *" */
#define SYS___set_tcb   329

/* syscall: "__get_tcb" ret: "void *" args: */
#define SYS___get_tcb   330

#define SYS_MAXSYSCALL  331

これが、syscall.hの一番最後の部分。生々しいな。次に足す時は、この番号って スタンプが押されている。OSを新しくしたら、毎回眺めてみる事にしよう。

securelevel(7)

上で出てきたセキュアーレベル。なんだか、rubyにもそんなのが有ったような。。。。

     -1 Permanently insecure mode
           -   init(8) will not attempt to raise the securelevel
           -   may only be set with sysctl(8) while the system is insecure
           -   otherwise identical to securelevel 0

      0 Insecure mode
           -   used during bootstrapping and while the system is single-user
           -   all devices may be read or written subject to their permissions
           -   system file flags may be cleared with chflags(2)

      1 Secure mode
           -   default mode when system is multi-user
           -   securelevel may no longer be lowered except by init
           -   /dev/mem and /dev/kmem may not be written to
           -   raw disk devices of mounted file systems are read-only
           -   system immutable and append-only file flags may not be removed
           -   the fs.posix.setuid sysctl(8) variable may not be changed
           -   the hw.allowpowerdown sysctl(8) variable may not be changed
           -   the net.inet.ip.sourceroute sysctl(8) variable may not be
               changed
           -   the machdep.kbdreset sysctl(8) variable may not be changed
           -   the ddb.console and ddb.panic sysctl(8) variables may not be
               raised
           -   the machdep.allowaperture sysctl(8) variable may not be raised
           -   gpioctl(8) may only access GPIO pins configured at system
               startup

      2 Highly secure mode
           -   all effects of securelevel 1
           -   raw disk devices are always read-only whether mounted or not
           -   settimeofday(2) and clock_settime(2) may not set the time
               backwards or close to overflow
           -   pf(4) filter and NAT rules may not be altered

もうひとつのエラー

確信犯なんだけど、sshでログインした普通の端末から、ddbを起動すると、そんな デバイスはサポートしてないと言われる。

# tty
/dev/ttyp0
# sysctl ddb.trigger=1
sysctl: ddb.trigger: Operation not supported by device

このエラーはカーネルが発している(と思われる)ので、man errnoを使って、 逆アセンブルし、エラーコードを得ておこう。ソースを見るのは、それからだ。

     19 ENODEV Operation not supported by device. An attempt was made to apply
             an inappropriate function to a device, for example, trying to
             read a write-only device such as a printer.

ENODEVが求めるコード。これを使って/sys/ddb/以下を探してみるのが筋。でも、 以前で調べが付いている。db_usrreq.c に、やっこさんは潜んでいるはず。

        case DBCTL_TRIGGER:
                if (newp && db_console) {
                        struct process *pr = curproc->p_p;

                        if (securelevel < 1 ||
                            (pr->ps_flags & PS_CONTROLT && cn_tab &&
                            cn_tab->cn_dev == pr->ps_session->s_ttyp->t_dev)) {
                                Debugger();
                                newp = NULL;
                        } else
                                return (ENODEV);
                }
                return (sysctl_rdint(oldp, oldlenp, newp, 0));
        default:
                return (EOPNOTSUPP);
        }

随分複雑な条件で判定してるな。条件を満たせばddbを起動出来るけど、満たさなかったら、 上記のエラーを出して終わりか。

条件にps_flags って、何となくプロセスがらみのコードが出てる。それに対応して、 sys/proc.hがincludeされてるな。cn_tabっていきなり出てきてるけど、sys/tty.hからの 回し者かしら?

proc.cから

#define PS_CONTROLT     0x00000001      /* Has a controlling terminal. */

実行しようとしたプロセスがターミナルをもってなきゃいけないって事だな。ダエモン君とか じゃダメって事。bgになってるプロセスでもターミナルは持ってるんかな。

struct process;
struct  session {
        int     s_count;                /* Ref cnt; pgrps in session. */
        struct  process *s_leader;      /* Session leader. */
        struct  vnode *s_ttyvp;         /* Vnode of controlling terminal. */
        struct  tty *s_ttyp;            /* Controlling terminal. */
        char    s_login[LOGIN_NAME_MAX];        /* Setlogin() name. */
};

こんな定義も有るな。

次は、cn_tabの方、tty.hに載っていなかったので、検索範囲をincludeの中って広げて みたら、dev/cons.hに出てきた。consって一瞬、lispとかに出て来る *あれ* かと思ったら、 この場合は、コンソールの事なのね。育ちがバレるな。

近辺を洗ってみると、

        dev_t   cn_dev;         /* major/minor of device */

こんなのも有ったし、ヘッダーと対になる、cons.c(なにせ、consですから、対は有りますよ)のcnopenには

        /*
         * always open the 'real' console device, so we don't get nailed
         * later.  This follows normal device semantics; they always get
         * open() calls.
         */
        cndev = cn_tab->cn_dev;
        if (cndev == NODEV)
                return (ENXIO);

こんなのも。上で出てきた判定式の3番目の項は、プロセスがconsoleを使っているかって 判定だな。

                            (pr->ps_flags & PS_CONTROLT && cn_tab &&
                            cn_tab->cn_dev == pr->ps_session->s_ttyp->t_dev)) {

一応cn_tabが有効か見ているのは、ENXIOを避けるためだろう。

     6 ENXIO Device not configured. Input or output on a special file referred
             to a device that did not exist, or made a request beyond the
             limits of the device.  This error may also occur when, for
             example, a tape drive is not online or no disk pack is loaded on
             a drive.

こういうエラーを避けるためだな。

実機検証

と言いつつ、使うのは、qemu動くOpenBSD。こいつを実験機にして、ddbを動かしてみる。 この実験機は、残念ながら、コンソールからddbが起動しない設定(コンパイル・オプション) になってる。

ならば、お手軽に、/etc/rcの

# rc.securelevel did not specifically set -1 or 2, so select the default: 1.
(($(sysctl -n kern.securelevel) == 0)) && sysctl kern.securelevel=1

この部分をコメントアウトして、いつでもddb可の、危ないマシンにしても良かったんだけど、 それじゃ親分に叱られそうなので、由緒正しく、カーネルを作り直す事にした。

が、途中でコンパイルエラーですよ。エラー内容を見たら、ヘッダーファイルが 足りないとの事。でも、その在処が、i386領域の中。そうなんだ。grepすると、色々な archが引っかかってくるんで、/sys/arch/amd64以外は全部消し去っていたんだ。

ここは、アーキテクチャ事に完全に独立してるかと思っていたんだけど、そうでも無いらしい。 amd64はi386の進化形です。だから、amd64なカーネルを作る時(あるいは、起動の 一時期)に、i386モードで動くっぽい。足りなかったものは、

[ob: ~]$ ls /sys/arch/i386/isa/
isa_machdep.h   nvram.h

これってBUGなんでしょうか? 親分に、ほうれんそう しといた方が良いのでしょうか? 自分的には、報告しといた方が良いように思うけど。判断は親分次第? こういうの、下駄を 預けちゃった方が楽だわな。

ddb_sysctlにBPを置いて、その時の状態をモニターしてみる。

(gdb) p *cn_tab
$3 = {
  cn_probe = 0x0,
  cn_init = 0x0,
  cn_getc = 0xffffffff8181de79 <wskbd_cngetc>,
  cn_putc = 0xffffffff81818398 <wsdisplay_cnputc>,
  cn_pollc = 0xffffffff8181844e <wsdisplay_pollc>,
  cn_bell = 0xffffffff8181e03c <wskbd_cnbell>,
  cn_dev = 3072,
  cn_pri = 1
}
(gdb) p *pr->ps_session->s_ttyp
   :
  t_dev = 1282,
    :
  t_winsize = {
    ws_row = 38,
    ws_col = 80,
    ws_xpixel = 0,
    ws_ypixel = 0
  },

これ、sshでログインした、ttyp2な端末での結果。

(gdb) p *cn_tab
$12 = {
  cn_probe = 0x0,
  cn_init = 0x0,
  cn_getc = 0xffffffff8181de79 <wskbd_cngetc>,
  cn_putc = 0xffffffff81818398 <wsdisplay_cnputc>,
  cn_pollc = 0xffffffff8181844e <wsdisplay_pollc>,
  cn_bell = 0xffffffff8181e03c <wskbd_cnbell>,
  cn_dev = 3072,
  cn_pri = 1
}
(gdb) p *pr->ps_session->s_ttyp
   :
  t_dev = 3072,
   :
  t_winsize = {
    ws_row = 25,
    ws_col = 80,
    ws_xpixel = 0,
    ws_ypixel = 0
  },

こちらは、vgaなコンソール端末。ttyC0って名前。sttyの結果は、それぞれの端末に 一致してた。端末属性まで、プロセスデータとして抱えこんでいるのね。納得です。

cn_dev とか t_dev が表している数字。メジャー番号とマイナー番号を、一つの整数に まとめたものなのね。(IPAdressのドット表記の2バイト版)

[ob: ~]$ ls -l /dev/{ttyC0,ttyp2}
crw-------  1 root   wheel   12,   0 Nov 17 14:42 /dev/ttyC0
crw-------  1 sakae  tty      5,   2 Nov 17 15:22 /dev/ttyp2

デバイスのメジャー/マイナー番号を得ておいて、デバイス番号に変換。

[ob: ~]$ bc
12 * 256 + 0
3072
5 * 256 + 2
1282

12.0とか、5.2と言えばいいのに。