카테고리 보관물: Linux

Ubuntu 22.04 Linux kernel 6.1.1로 올리기

Ubuntu 22.04에서 Linux kernel 6.1.1 빌드해서 설정한 내용 정리이다. 굳이 실험정신을 억누르지 못하는게 아니라면 간단히 deb package를 다운로드 받아서 설치해도 된다.

Prerequisites

컴파일에 필요한 tool들을 설치한다.

sudo apt-get install libncurses-dev gawk flex bison openssl libssl-dev dkms libelf-dev libudev-dev libpci-dev libiberty-dev autoconf llvm

Linux kernel code를 다운로드 받고 합축을 해제해 둔다.

wget -c https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.1.tar.xz && tar xvf ./linux-6.1.1.tar.xz

Kernel config 설정 및 build 실행

Linux kernel config를 하나하나 설정하려면 시간이 오래 걸리니 현재 잘 동작하는 있는 kernel 설정 파일을 가져와서 설정하자.

cd ./linux-6.1.1
cp /boot/config-$(uname -r) ./.config

fakeroot debian/rules clean
make olddefconfig

추가로, 다음과 같이 복사된 .config를 열어서 KEY 설정을 주석으로 없애주는게 좋다. 그렇지 않으면 “No rule to make target ‘debian/canonical-certs.pem’, needed by ‘certs/x509_certificate_list’. Stop.” 오류가 발생하면서 빌드가 멈출 것이다.

#                                                                                                   
# Certificates for signature checking                                                               
#                                                                                                   
...                                                              
CONFIG_SYSTEM_TRUSTED_KEYS="" #COMMENT OUT "debian/canonical-certs.pem"                                              
...                                                            
CONFIG_SYSTEM_REVOCATION_KEYS="" #COMMENET OUT "debian/canonical-revoked-certs.pem"   

모든 설정이 완료되면 빌드를 실행한다.

fakeroot debian/rules binary -j$(nproc)

Build된 패키지 설치 및 확인

빌드가 완료되면 다음과 같이 4개의 deb file들이 source code의 상위 디렉토리에 생성되었는지를 확인하고 apt 명령어로 설치 후 system을 reboot한다.

ls ../*.deb
  ../linux-headers-6.1.1_6.1.1-2_amd64.deb
  ../linux-image-6.1.1-dbg_6.1.1-2_amd64.deb
  ../linux-image-6.1.1_6.1.1-2_amd64.deb
  ../linux-libc-dev_6.1.1-2_amd64.deb

sudo apt install ../*.deb
sudo reboot

BIOS로 진입해서 secure boot을 disable하고 GRUB에서 새로 설치한 kernel을 선택해서 부팅한 후 다음과 같이 확인할 수 있다.

uname -a
Linux <machine-name> 6.1.1 #2 SMP PREEMPT_DYNAMIC Thu Dec 29 13:15:23 KST 2022 x86_64 x86_64 x86_64 GNU/Linux

CARLA client를 build해야 하는데 clang-8이 없댄다.

2022년 9월 현재 CARLA simulator의 최신 버전인 0.9.13으로 PythonAPI를 빌드하려고 하면 clang-8 version을 요구하는데 Ubuntu 22.04에서는 clang-8의 repository를 추가하고 설치 시도하면 다음과 같이 의존성 오류가 생긴다.

The following packages have unmet dependencies:
 clang-8 : Depends: libllvm8 (>= 1:8~svn298832-1~) but it is not going to be installed
           Depends: libstdc++-5-dev but it is not installable
           Depends: libgcc-5-dev but it is not installable
           Depends: libobjc-5-dev but it is not installable
           Depends: libclang-common-8-dev (= 1:8.0.1+svn369350-1~exp1~20200112113617.82) but it is not going to be installed
           Depends: libclang1-8 (= 1:8.0.1+svn369350-1~exp1~20200112113617.82) but it is not going to be installed
           Recommends: llvm-8-dev but it is not going to be installed
           Recommends: libomp-8-dev but it is not going to be installed
 lld-8 : Depends: libllvm8 (= 1:8.0.1+svn369350-1~exp1~20200112113617.82) but it is not going to be installed
E: Unable to correct problems, you have held broken packages.

그리고 아쉽게도 하위 호환에 문제가 있는 것인지 설치 가능한 최하 버전인 clang-10으로는 CARLA client build가 되지 않는다. 빌드 스크립트에서 버전 점검하는 부분을 건너 뛰도록 수정하고 clang-10으로 강제 빌드를 시도해봤더니 역시나 빌드오류가 나면서 일이 커질것 같다는 느낌이 강하게 든다.

일일이 빌드오류 잡는 삽질을 하고 ‘CARLA client build 삽질기’를 포스팅 할 수도 있었겠지만 이번에는 문명의 이기인 Docker를 한번 누려 보기로했다.

먼저, Docker로 clang-8 설치가 가능한 Ubuntu 18.04에서 빌드를 수행한 다음(참고 CARLA – Linux Build) 빌드가 완료되면 Python client package를 host에 설치한다.

  • Docker로 Ubuntu18.04를 설치하고 CARLA의 소스 코드위치를 /carla로 마운트하여 실행한다.
$ docker pull ubuntu:18.04
$ docker run -ti -v <carla-root-path>:/carla ubuntu:18.04 /bin/bash
  • Clang-8의 repository를 추가하고 관련된 패키지들을 설치한다.
# Clang-8 repository 추가.
DOCKER# apt-get update &&
apt-get install wget software-properties-common &&
add-apt-repository ppa:ubuntu-toolchain-r/test &&
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|apt-key add - &&
apt-add-repository "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-8 main" &&
apt-get update

# Install packages.
DOCKER# apt-get install -y build-essential clang-8 lld-8 g++-7 cmake ninja-build libvulkan1 python python-pip python-dev python3-dev python3-pip libpng-dev libtiff5-dev libjpeg-dev tzdata sed curl unzip autoconf libtool rsync libxml2-dev git
  • Clang-8을 default로 설정한다.
# Set clang-8 as a default clang.
DOCKER# update-alternatives --install /usr/bin/clang++ clang++ /usr/lib/llvm-8/bin/clang++ 180 && update-alternatives --install /usr/bin/clang clang /usr/lib/llvm-8/bin/clang 180
  • Host machine의 python과 동일한 버전(3.8)을 docker에도 설치하고 관련된 Python package들을 pip로 설치해준다. 특별히 setuptools package는 버전의 영향을 받으므로 확인된 버전(47.3.1)을 명시해 준다.
DOCKER# apt install -y python3.8 python3.8-dev

# PIP upgrade
DOCKER# pip3 install --upgrade pip && pip install --upgrade pip

# 중요. Python3의 setuptools version이 안맞으면 빌드에 실패할 수 있으니 버전명을 명시해 준다.
DOCKER# pip2 install setuptools &&
pip3 install -Iv setuptools==47.3.1 &&
pip2 install distro &&
pip3 install distro &&
pip2 install wheel &&
pip3 install wheel auditwheel
  • 이제 해당 버전으로 docker에서 build를 시도 한다.
DOCKER# cd /carla
DOCKER# make PythonAPI ARGS="--python-version=3.8"
  • /carla/PythonAPI/carla/dist 아래에 소스에 포함되어 있는 3.6용 package외에 새롭게 빌드한 3.8용 *.whl, *.egg file들이 생성된 것을 확인하고 host system에 설치해 준다.
DOCKER# ls /carla/PythonAPI/carla/dist/
carla-0.9.13-cp36-cp36m-linux_x86_64.whl  carla-0.9.13-py3.6-linux-x86_64.egg
carla-0.9.13-cp38-cp38m-linux_x86_64.whl  carla-0.9.13-py3.8-linux-x86_64.egg

# CARLA client 설치
$ sudo apt install -y <carla-root-path>/PythonAPI/carla/dist/carla-0.9.13-py3.8-linux-x86_64.egg

Docker로 OpenGrok 설치

잘 쓰고 있던 OpenGrok 서버가 갑자기 맛이 가는 바람에 부랴부랴 대안을 찾아야 했는데 마땅한 서버가 없어서 로컬 머신에 Docker로 설치하는 방법을 찾아 보았다. 여기 소개된 내용은 Docker Hub에서 자세한 설명을 찾을 수 있다.

Docker가 설치되어 있다면 command창에서 다음의 명령으로 OpenGrok docker를 pull한다.

docker pull opengrok/docker

Pulling이 끝나면 목적에 소스와 indexing결과가 저장될 공간을 만들어 준다. src에는 분석할 소스를 넣고 bin에는 편의를 위한 스크립트를 넣을 예정이다.

mkdir -p ~/opengrok/bin
mkdir -p ~/opengrok/src
mkdir -p ~/opengrok/etc
mkdir -p ~/opengrok/data

이제, 8080 port에 접속 설정을 하고 위에서 만든 volume들을 docker에 마운트 시켜준다. Git server에 접근하기 위해 키 관련 설정을 해주어야 하는데, 귀찮아서 그냥 .ssh 디렉토리를 마운트 시켜 주었다.

docker run -d \
    --name opengrok \
    -p 8080:8080/tcp \
    -v ~/opengrok/bin/:/opengrok/bin/ \
    -v ~/opengrok/src/:/opengrok/src/ \
    -v ~/opengrok/etc/:/opengrok/etc/ \
    -v ~/opengrok/data/:/opengrok/data/ \
    -v ~/.ssh:/root/.ssh \
    opengrok/docker:latest

이제 해당 서버의 콘솔을 열고 인덱싱 명령을 수행하면 된다. GUI가 없다면 다음의 명령으로 실행 중인 docker에 접속할 수 있다.

docker exec -it <docker_container_id> bash

서버에 접속한 후 인덱싱을 수행하는 명령어는 다음과 같다.

export OPENGROK_DIR=/opengrok
java \
    -Djava.util.logging.config.file=$OPENGROK_DIR/etc/logging.properties \
    -Xmx1024m \
    -jar $OPENGROK_DIR/lib/opengrok.jar \
    -c /usr/local/bin/ctags \
    -s $OPENGROK_DIR/src -d $OPENGROK_DIR/data -H -P -S -G \
    -W $OPENGROK_DIR/etc/configuration.xml -U http://localhost:8080/

인덱싱이 끝나면 웹브라우져에서 http://localhost:8080으로 접속하면 된다.

위의 인덱싱 명령어가 너무 길어서 입력하기 힘들기 때문에 source code를 업데이트하고 인덱싱 하는 과정을 묶어서 다음과 같이 스크립트로 만들고 ~/opengrok/bin 안에 넣어 두면 편리하게 사용할 수 있다.

Linux에서 메모리 포인터의 유효성 검증

Windows에서와 달리 Linux환경에서는 딱히 포인터의 유효성을 검증할 수 있는 system call이 없다. 이 포스팅은 Linux환경에서 이와 유사한 기능을 구현하기 위해 “정보의 바다”에서 찾은 내용들을 정리해 둔 것이다.

1. _etext를 이용하는 방법

첫번째 방법은 Define and use a pointer validation function 이라는 위키문서에서 가져온 것인데 컴파일러가 생성하는 text 영역의 시작점을 이용해서 포인터 값이 이를 침범 하는지 여부를 검사하고 유효성을 판단한다. 하지만 위 링크의 커멘트에도 나와 있듯이 시스템에 따라 동작하지 않을 수도 있으므로 reliable 한 구현이라 볼 수는 없다.

bool isValidPointer_ET(void *ptr) {
    extern const char _etext;
    return (ptr != nullptr) && ((const char*)ptr > &_etext);
}

2. msync()를 이용하는 방법

Checking whether a pointer is valid in Linux라는 블로그 포스트에 소개된 방법으로 매핑된 메모리 공간을 동기화 할 때 쓰는 msync() 시스템 콜을 호출 하면서 유효하지 않은 page 시작 주소를 넘겨 주면 0이 아닌 음수 값을 반환하는 것을 이용하는 방법이다. (이 경우 errno에 ENOMEM이 설정된다)

#include <sys/mman.h>
#include <unistd.h>

bool isValidPointer_MS(void *ptr) {
    const size_t pageSize = sysconf(_SC_PAGESIZE);
    void *basePtr = (void *)((((size_t)ptr) / pageSize) * pageSize);
    return msync(basePtr, pageSize, MS_SYNC) == 0;
}

3. mincore()를 이용하는 방법

Stackoverflow에 올려진 Testing pointers for validity (C/C++)라는 질문에 대한 답변에 있는 아이디어 중 하나인데 메모리 페이지의 swap 상태를 확인해서 반환해 주는 mincore()을 이용하는 방법이다. 해당 답변에는 다른 아이디어 들도 있으니 필요에 따라 참고.

#include <sys/mman.h>
#include <unistd.h>

bool isValidPointer_MC(void *ptr) {
    unsigned char vec = 0;
    const size_t pageSize = sysconf(_SC_PAGESIZE);
    void *basePtr = (void *)((((size_t)ptr) / pageSize) * pageSize);
    int ret = mincore(basePtr, pageSize, &vec);
    return (ret == 0 && ((vec & 0x1) == 0x1));
}

시험 결과와 결론

위의 함수들에 대해 Linux환경에서 전역 변수 포인터, 지역 변수 포인터, 널 포인터, 널 포인터는 아니지만 명백하게 무효한 포인터(0x04 같은), 동적 할당된 공간에 대한 유효성 여부는 잘 동작한다.

하지만, 이미 해제된 포인터나 할당되지 않은 heap 공간 내의 임의 주소에 대해서는 제대로 유효성 여부를 판단하지 못하고, 주소 범위가 유효 하다면 포인터 역시 유효 하다고 판단하는 오류가 세가지 구현 모두에 있다.

    // 해제된 heap공간에 대한 유효성 여부 확인. 모두 실패함.
    unsigned int* dynamicVar = new unsigned int[100];
    delete[] dynamicVar;
    EXPECT_FALSE(isValidPointer_ET(dynamicVar));
    EXPECT_FALSE(isValidPointer_MS(dynamicVar));
    EXPECT_FALSE(isValidPointer_MC(dynamicVar));


    // 할당 되지 않은 Heap공간 내의 임의 포인터에 대한 유효성 확인. 모두 실패함.
    unsigned int* dynamicUnallocVar = dynamicVar + 100;
    EXPECT_FALSE(isValidPointer_ET(dynamicUnallocVar));
    EXPECT_FALSE(isValidPointer_MS(dynamicUnallocVar));
    EXPECT_FALSE(isValidPointer_MC(dynamicUnallocVar)); 

즉, 위의 구현 들은 주어진 포인터가 유효한 메모리 공간내에 속하는지는 확인할 수 있어도, 동적 할당 영역의 메모리 포인터가 실제 read/write 가능한 상태인지 여부는 정확히 반환 할 수 없다.

시험에 사용한 code는 여기에 붙여 둔다.

Lightsail Ubuntu 20.04 업그레이드 후 ssh 접속 불가 현상

Lightsail의 이미지를 Ubuntu 20.04 LTS로 업그레이드 한 후 web 환경에서 SSH 접속이 되지 않아서 또 망했다며 머리를 쥐어 뜯고 있는데, 우연히, 당연히 안 될 거라고 생각했던 터미널 프로그램을 통한 SSH 접속은 또 되는 기현상을 발견했다. 이 대로도 나쁘진 않지만 좀 찜찜 하기도 하고 해서 좀 더 찾아 보았다.

접속 오류가 발생할 때의 로그를 보면 다음과 같은데,

$ cat /var/log/auth.log|tail
...
sshd[4528]: userauth_pubkey: certificate signature algorithm ssh-rsa: signature algorithm not supported [preauth]
...

이 문제의 해결책에 대해 아주 자세히 설명된 Use RSA CA Certificates with OpenSSH 8.2에 따르면, (Ubuntu 20.04에 포함된) OpenSSH 8.2 부터는 보안 문제로 SHA-1 기반인 ssh-rsa가 기본 CA signature 항목에서 빠지면서 이러한 문제가 발생하게 된다고 한다. 해결 방법은 CASignatureAlgorithms에 ssh-rsa를 지원하도록 명시하는 것이다.

$ cat /etc/ssh/sshd_config|tail
...
# Use RSA CA cert.
# https://ibug.io/blog/2020/04/ssh-8.2-rsa-ca/
CASignatureAlgorithms ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa

위의 링크에서 제안하는 대로 sshd_config 파일에 CASignatureAlgorithms 항목을 위와 같이 추가 하고 sshd service를 재 실행 하고나니, web 환경 SSH가 잘 동작하게 되었다. 물론, 터미널도 그대로 잘 된다.