This is a series of blog posts, and I will continue to provide you with the best possible insight into Android source codeGithub serial address

preface

On an article about the Linux systems perform the final performs initialization under the root directory of the init file, init is an executable program, its source in the platform/system/core/init/init. CPP. As we’ve seen before, init is the first process in user space, and it’s the parent of most apps that we’re familiar with, and init’s entry function is the main function, which does a lot of things, and it’s divided into three parts

  • Init process phase 1
  • Init process phase 2
  • Init. rc file parsing

Because it’s a lot of stuff, I’m going to cover init in three chapters, and this article is just going to cover the first phase, which includes the following

  • Ueventd/Watchdogd jumps and setting environment variables
  • Mount the file system and create a directory
  • Initialize log output and mount partition devices
  • Enable the SELinux security policy
  • Start preparation for stage 2

The files covered in this article

platform/system/core/init/init.cpp
platform/system/core/init/ueventd.cpp
platform/system/core/init/watchdogd.cpp
platform/system/core/init/log.cpp
platform/system/core/base/logging.cpp
platform/system/core/init/init_first_stage.cpp
platform/external/selinux/libselinux/src/callbacks.c
platform/external/selinux/libselinux/src/load_policy.c
platform/external/selinux/libselinux/src/getenforce.c
platform/external/selinux/libselinux/src/setenforce.c
platform/external/selinux/libselinux/src/android/android.c
Copy the code

Ueventd/Watchdogd jump and setting environment variables

/* * 1. In C++, the main function takes two arguments, argc is the number of arguments, and the second argument is the list of arguments *. Ueventd_main: watchdogd: watchdogd_main */
int main(int argc, char** argv) {

    /* * 1. STRCMP is a function that compares strings and returns 0 * 2. In C++ 0 can also mean false * 3. /sdcard/miui_recovery/backup
    if (!strcmp(basename(argv[0]), "ueventd")) { // when argv[0] is ueventd, STRCMP is 0! STRCMP to 1
    Ueventd is responsible for creating device nodes, setting permissions, and other tasks
        return ueventd_main(argc, argv);
    }

    if (!strcmp(basename(argv[0]), "watchdogd")) {// Watchdogd is used to restart the system when a problem occurs
        return watchdogd_main(argc, argv);
    }

    if (REBOOT_BOOTLOADER_ON_PANIC) {
        install_reboot_signal_handlers(); // Initializes the signal to restart the system, internally registers the signal with sigAction, and restarts the system when the signal is heard
    }

    add_environment("PATH", _PATH_DEFPATH);// Register the environment variable PATH
    //#define	_PATH_DEFPATH	"/sbin:/system/sbin:/system/bin:/system/xbin:/odm/bin:/vendor/bin:/vendor/xbin"

Copy the code

1.1 ueventd_main

Defined in the platform/system/core/init/ueventd CPP

The “/dev” directory does not exist in the image of the Android root file system, which was created dynamically after the init process started.

Therefore, the burden of creating device node files in Android falls on the init process. To do this, the init process creates a child process, ueventD, and delegates the creation of the device node file to Ueventd. Ueventd creates device node files in two ways.

The first method corresponds to Cold Plug. Based on pre-defined device information, device node files are created after UEventD is started. This class of device node files is also known as static node files.

The second method corresponds to Hot Plug. When a device is inserted into the USB port during system running, UEventD receives the event and dynamically creates device node files for the inserted device. This class of device node files is also known as dynamic node files.

int ueventd_main(int argc, char **argv)
{
    /* * init sets the umask to 077 for forked processes. We need to * create files with exact permissions, without modification by * the umask. */
    umask(000); // Set the default value for new files. This is the opposite of chmod, which equals 666 permissions for new files

    /* Prevent fire-and-forget children from becoming zombies. * If we should need to wait() for some children in the future  * (as opposed to none right now), double-forking here instead * of ignoring SIGCHLD may be the better solution. */
    signal(SIGCHLD, SIG_IGN);// Ignore the child process termination signal

    InitKernelLogging(argv); // Initialize log output

    LOG(INFO) << "ueventd started!";

    selinux_callback cb;
    cb.func_log = selinux_klog_callback;
    selinux_set_callback(SELINUX_CB_LOG, cb);// Register selinux-related callback functions for printing logs

    ueventd_parse_config_file("/ueventd.rc"); // Parse the.rc file, more on this later
    ueventd_parse_config_file("/vendor/ueventd.rc");
    ueventd_parse_config_file("/odm/ueventd.rc");

    /*
     * keep the current product name base configuration so
     * we remain backwards compatible and allow it to override
     * everything
     * TODO: cleanup platform ueventd.rc to remove vendor specific
     * device node entries (b/34968103)
     */
    std: :string hardware = android::base::GetProperty("ro.hardware"."");
    ueventd_parse_config_file(android::base::StringPrintf("/ueventd.%s.rc", hardware.c_str()).c_str());

    device_init();// Create a socket to receive uEvents, and "cold plug" the driver registered with /sys/ at kernel startup to create the corresponding node file.

    pollfd ufd;
    ufd.events = POLLIN;
    ufd.fd = get_device_fd();// Get the socket created in device_init

    while (true) {// Open an unlimited loop, always listen to the driver
        ufd.revents = 0;
        int nr = poll(&ufd, 1.- 1);// Listen for uEvents from drivers
        if (nr <= 0) {
            continue;
        }
        if (ufd.revents & POLLIN) {
            handle_device_fd();// The driver is "hot-swappable" to create the corresponding node file.}}return 0;
}

Copy the code

1.2 watchdogd_main

Defined in the platform/system/core/init/watchdogd CPP

“Watchdog” itself is a timer circuit, the internal will be constantly timing (or counting) operation, the computer system and “watchdog” has two pins connected, normal operation will send signals to the “watchdog” through one of the pins at intervals. “Watchdog” receives the signal after the timer will be reset and start the timer, and once the system appear problem, enter a state of infinite loop or any obstruction, can’t timely send signals to let “watchdog timer” reset, when at the end of the time, “watchdog” will be sent via another pin to system “reset”, let the system reboot

Watchdogd_main is primarily a timer, and DEV_NAME is the pin

int watchdogd_main(int argc, char **argv) {
    InitKernelLogging(argv);

    int interval = 10;
    /* * the function of atoi in C++ is to convert a string to a value */
    if (argc >= 2) interval = atoi(argv[1]);

    int margin = 10;
    if (argc >= 3) margin = atoi(argv[2]);

    LOG(INFO) << "watchdogd started (interval " << interval << ", margin " << margin << ")!";

    int fd = open(DEV_NAME, O_RDWR|O_CLOEXEC); // Open the file /dev/watchdog
    if (fd == - 1) {
        PLOG(ERROR) << "Failed to open " << DEV_NAME;
        return 1;
    }

    int timeout = interval + margin;
    /* * IOCtl is a device driver for device I/O channel management function,WDIOC_SETTIMEOUT is set timeout */
    int ret = ioctl(fd, WDIOC_SETTIMEOUT, &timeout);
    if (ret) {
        PLOG(ERROR) << "Failed to set timeout to " << timeout;
        ret = ioctl(fd, WDIOC_GETTIMEOUT, &timeout);
        if (ret) {
            PLOG(ERROR) << "Failed to get timeout";
        } else {
            if (timeout > margin) {
                interval = timeout - margin;
            } else {
                interval = 1;
            }
            LOG(WARNING) << "Adjusted interval to timeout returned by driver: "
                         << "timeout " << timeout
                         << ", interval " << interval
                         << ", margin "<< margin; }}while (true) {// Write a blank character to the file at regular intervals. This is the key of the watchdog
        write(fd, "".1); sleep(interval); }}Copy the code

1.3 install_reboot_signal_handlers

Defined in the platform/system/core/init/init. CPP

This function sets the behavior of various semaphores such as SIGABRT and SIGBUS to SA_RESTART. Once these signals are heard, the system will restart

static void install_reboot_signal_handlers(a) {
    // Instead of panic'ing the kernel as is the default behavior when init crashes,
    // we prefer to reboot to bootloader on development builds, as this will prevent
    // boot looping bad configurations and allow both developers and test farms to easily
    // recover.
    struct sigaction action;
    memset(&action, 0.sizeof(action));
    sigfillset(&action.sa_mask);// Add all signals to the signal set
    action.sa_handler = [](int) {
        // panic() reboots to bootloader
        panic(); // Restart the system
    };
    action.sa_flags = SA_RESTART;
    sigaction(SIGABRT, &action, nullptr);
    sigaction(SIGBUS, &action, nullptr);
    sigaction(SIGFPE, &action, nullptr);
    sigaction(SIGILL, &action, nullptr);
    sigaction(SIGSEGV, &action, nullptr);
#if defined(SIGSTKFLT)
    sigaction(SIGSTKFLT, &action, nullptr);
#endif
    sigaction(SIGSYS, &action, nullptr);
    sigaction(SIGTRAP, &action, nullptr);
}
Copy the code

1.4 add_environment

Defined in the platform/system/core/init/init. CPP

This function places a key-value pair into a Char array, replacing it if there is a key in the array, and inserting it if there is no key in the array, much like a Map in Java

/* add_environment - add "key=value" to the current environment */
int add_environment(const char *key, const char *val)
{
    size_t n;
    size_t key_len = strlen(key);

    /* The last environment entry is reserved to terminate the list */
    for (n = 0; n < (arraysize(ENV) - 1); n++) {

        /* Delete any existing entry for this key */
        if(ENV[n] ! =NULL) {
        /* * STRCSPN in C++ is used to return the indexof a character, equivalent to String indexof */
            size_t entry_key_len = strcspn(ENV[n], "=");
            if ((entry_key_len == key_len) && (strncmp(ENV[n], key, entry_key_len) == 0)) { // If the keys are the same, delete the corresponding data
                free((char*)ENV[n]);
                ENV[n] = NULL; }}/* Add entry if a free slot is available */
        if (ENV[n] == NULL) { // If there is no corresponding key, insert data
            char* entry;
            asprintf(&entry, "%s=%s", key, val);
            ENV[n] = entry;
            return 0;
        }
    }

    LOG(ERROR) << "No env. room to store: '" << key << "' : '" << val << "'";

    return - 1;
}
Copy the code

Mount a file system and create a directory


    bool is_first_stage = (getenv("INIT_SECOND_STAGE") = =nullptr);// Check whether the environment variable INIT_SECOND_STAGE exists

    Init's main method is executed twice, controlled by is_first_stage, which is what the first stage does */
    if (is_first_stage) {// Execute only once, because INIT_SECOND_STAGE is set in the method body
        boot_clock::time_point start_time = boot_clock::now();

        // Clear the umask.
        umask(0); // Clear file permissions

        // Get the basic filesystem setup we need put together in the initramdisk
        // on / and then we'll let the rc file figure out the rest.
        mount("tmpfs"."/dev"."tmpfs", MS_NOSUID, "mode=0755");
        mkdir("/dev/pts".0755);
        mkdir("/dev/socket".0755);
        mount("devpts"."/dev/pts"."devpts".0.NULL);
        #define MAKE_STR(x) __STRING(x)
        mount("proc"."/proc"."proc".0."hidepid=2,gid=" MAKE_STR(AID_READPROC));
        // Don't expose the raw commandline to unprivileged processes.
        chmod("/proc/cmdline".0440);
        gid_t groups[] = { AID_READPROC };
        setgroups(arraysize(groups), groups);
        mount("sysfs"."/sys"."sysfs".0.NULL);
        mount("selinuxfs"."/sys/fs/selinux"."selinuxfs".0.NULL);
        mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1.11));
        mknod("/dev/random", S_IFCHR | 0666, makedev(1.8));
        mknod("/dev/urandom", S_IFCHR | 0666, makedev(1.9)); . }... }Copy the code

2.1 the mount

Mount is used to mount file systems. Mount belongs to the Linux system call

int mount(const char *source, const char *target, const char *filesystemtype,
unsigned long mountflags, const void *data);
Copy the code

Parameters:

Source: The file system to be mounted, usually a device name.

Target: indicates the target directory to which the file system is mounted.

Filesystemtype: filesystemtype, which can be “ext2”, “msdos”, “proc”, “NTFS”, “iso9660″…

Mountflags: specifies the read and write flags of the file system. The possible values are as follows

parameter meaning
MS_BIND Perform the bind mount to make the file or subdirectory tree visible at another point in the file system.
MS_DIRSYNC Synchronize directory updates.
MS_MANDLOCK Allows forced locks on files.
MS_MOVE Move the subtree.
MS_NOATIME Do not update access times on files.
MS_NODEV Access to device files is not allowed.
MS_NODIRATIME Update access times on directories are not allowed.
MS_NOEXEC It is not allowed to execute programs on the mounted file system.
MS_NOSUID Program execution does not follow set-user-id and set-group-id bits.
MS_RDONLY Specifies that the file system is read-only.
MS_REMOUNT Reload the file system. This allows you to change the mountflag and data of an existing file system without having to unmount and then mount the file system.
MS_SYNCHRONOUS Synchronize file updates.
MNT_FORCE Forcibly unmount, even if the file system is busy.
MNT_EXPIRE Mark the mount point as obsolete.

Data: parameter specific to the file system

During init initialization, Android mounts TMPFS, Devpts, Proc, SYSfs, and Selinuxfs file systems.

TMPFS is a virtual memory file system that stores all files in virtual memory. If you unmount the TMPFS file system, all the contents under it will disappear. TMPFS can use either RAM or swap, varying the size according to your actual needs. The speed of TMPFS is amazing, because it is, after all, hosted in RAM, even with swap partitions, and still performs very well. Because TMPFS resides in RAM, its contents are not persistent. When the power goes off, the content of TMPFS disappears, which is the root reason it is called TMPFS.

The devpts file system provides a standard interface for pseudo-terminals, and its standard mount point is /dev/pts. As soon as the primary pTY composite device /dev/ptmx is opened, a new PTY device file is dynamically created under /dev/pts.

The Proc file system is a very important virtual file system that can be viewed as an interface to the kernel’s internal data structures, allowing us to obtain information about the system and modify specific kernel parameters at run time.

Like the Proc file system, the SYSFS file system is a virtual file system that does not occupy any disk space. It is usually mounted in the /sys directory. The sysfs file system, introduced by the Linux2.6 kernel, organizes devices and buses connected to the system into a hierarchical file that can be accessed in user space

Selinuxfs is also a virtual file system. It is usually mounted to the /sys/fs/selinux directory to store selinux security policy files

2.2 mknod

Mknod is used to create device files in Linux

int mknod(const char* path, mode_t mode, dev_t dev) {}Copy the code

Parameters: path: indicates the directory where the device resides. Mode: indicates the type of the device and the possible type of the read/write flag

parameter meaning
S_IFMT Type of file indicates the mask of the file type
S_IFREG Regular Ordinary file
S_IFBLK Block Special Block device file
S_IFDIR Directory Directory file
S_IFCHR Character Special Character device file
S_IFIFO Fifo pipe file
S_IFNAM Special named file Special file
S_IFLNK Symbolic Link The symbolic link file

Dev represents the device and is created by the makedev(1, 9) function, where 9 is the primary device number and 1 is the secondary device number

2.3 Other Commands

Mkdir is also a Linux system call that creates a directory. The first parameter is the directory path and the second parameter is the read and write permission

The chmod command is used to change the read and write permission of a file or directory

Setgroups is used to add groups specified in the list array to the current process’s group Settings

To understand permissions, you need to understand the concept of “users and groups”

The Linux system can have multiple users, and multiple users can belong to the same group. The concept of user and group is just like us and family. People belong to a part of the family, and users belong to a group

Drwxr-xr-x 7 Foxleezh Foxleezh 4096 2月 24 14:31. AndroidCopy the code

The first foxleezh is the owner, so foxleezh here is a user, something like foxleezh

The second Foxleezh represents all user groups of the file, where Foxleezh represents a group, similar to the Foxleezh family

Dwxr-xr-x is divided into four parts, d for directory (files are represented by -), WXR for owner permissions, Xr for all user groups of files, and X for other user permissions

  • W – Indicates the write permission. The value is 2
  • X – Indicates the execution permission. The value is 1
  • Dwxr-xr-x = 751; dwxR-xR-x = 751; dwxR-xR-x = 751;

The 0 before 0755 is associated with suID and GUID

  • Suid means that other users have the same permissions as the file owner, represented by 4
  • Guid means that other users have the same permissions as all user groups of the file, denoted by 2

Initialize log output and mount partition devices

if (is_first_stage) {

          ...

        // Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually
        // talk to the outside world...
        InitKernelLogging(argv);

        LOG(INFO) << "init first stage started!";

        if(! DoFirstStageMount()) { LOG(ERROR) <<"Failed to mount required partitions early ...";
            panic();// Restart the system}... }Copy the code

3.1 InitKernelLogging

Defined in the platform/system/core/init/log. The CPP

InitKernelLogging first redirects standard input and output to “/sys/fs/selinux/null” and then calls InitLogging to initialize the log logging system

void InitKernelLogging(char* argv[]) {
    // Make stdin/stdout/stderr all point to /dev/null.
    int fd = open("/sys/fs/selinux/null", O_RDWR); // Open the file
    if (fd == - 1) {
        int saved_errno = errno;
        android::base::InitLogging(argv, &android::base::KernelLogger);
        errno = saved_errno;
        PLOG(FATAL) << "Couldn't open /sys/fs/selinux/null";
    }
    /* * dup2(int old_fd, int new_fd) copies file descriptors from old to new. * 0, 1, 2 are bound to null devices
    dup2(fd, 0); // Redirect the standard input stdin
    dup2(fd, 1);// Redirect stdout
    dup2(fd, 2);// Redirect the standard error stderr
    if (fd > 2) close(fd);

    android::base::InitLogging(argv, &android::base::KernelLogger);// Initialize the log
}
Copy the code

3.2 InitLogging

Defined in the platform/system/core/base/logging. The CPP

InitLogging sets up logger and aborter handlers, and then sets the logging system output level

void InitLogging(char* argv[], LogFunction&& logger, AbortFunction&& aborter) {
/* * in C++ foo(STD ::forward
      
       (arg)) means to pass arg to foo as its original lvalue or rvalue, LogFunction& is lvalue, and LogFunction&& is rvalue */
      
  SetLogger(std::forward<LogFunction>(logger)); // Set the logger handler
  SetAborter(std::forward<AbortFunction>(aborter));// Set the aborter handler

  if (gInitialized) {
    return;
  }

  gInitialized = true;

  // Stash the command line for later use. We can use /proc/self/cmdline on
  // Linux to recover this, but we don't have that luxury on the Mac/Windows,
  // and there are a couple of argv[0] variants that are commonly used.
  if(argv ! =nullptr) {
    std::lock_guard<std::mutex> lock(LoggingLock());
    ProgramInvocationName() = basename(argv[0]);
  }

  const char* tags = getenv("ANDROID_LOG_TAGS");// Get the current log output level of the system
  if (tags == nullptr) {
    return;
  }

  std: :vector<std: :string> specs = Split(tags, ""); // Split tags into arrays with Spaces
  for (size_t i = 0; i < specs.size(); ++i) {
    // "tag-pattern:[vdiwefs]"
    std: :string spec(specs[i]);
    if (spec.size() == 3 && StartsWith(spec, "*:")) { // If the number of characters is 3 and begins with *:
     // Set the level of log output according to the third character (e.g. *:d, DEBUG level)
      switch (spec[2]) {
        case 'v':
          gMinimumLogSeverity = VERBOSE;
          continue;
        case 'd':
          gMinimumLogSeverity = DEBUG;
          continue;
        case 'i':
          gMinimumLogSeverity = INFO;
          continue;
        case 'w':
          gMinimumLogSeverity = WARNING;
          continue;
        case 'e':
          gMinimumLogSeverity = ERROR;
          continue;
        case 'f':
          gMinimumLogSeverity = FATAL_WITHOUT_ABORT;
          continue;
        // liblog will even suppress FATAL if you say 's' for silent, but that's
        // crazy!
        case 's':
          gMinimumLogSeverity = FATAL_WITHOUT_ABORT;
          continue;
      }
    }
    LOG(FATAL) << "unsupported '" << spec << "' in ANDROID_LOG_TAGS (" << tags
               << ")"; }}Copy the code

3.3 KernelLogger

Defined in the platform/system/core/base/logging. The CPP

There is a call in the InitKernelLogging method

android::base::InitLogging(argv, &android::base::KernelLogger);
Copy the code

/dev/ KMSG/KernelLogger /dev/ KMSG/KernelLogger /dev/ KMSG

void KernelLogger(android::base::LogId, android::base::LogSeverity severity,
                  const char* tag, const char*, unsigned int.const char* msg) {
  // clang-format off
  static constexpr int kLogSeverityToKernelLogLevel[] = {
      [android::base::VERBOSE] = 7.// KERN_DEBUG (there is no verbose kernel log
                                                 // level)
      [android::base::DEBUG] = 7.// KERN_DEBUG
      [android::base::INFO] = 6.// KERN_INFO
      [android::base::WARNING] = 4.// KERN_WARNING
      [android::base::ERROR] = 3.// KERN_ERROR
      [android::base::FATAL_WITHOUT_ABORT] = 2.// KERN_CRIT
      [android::base::FATAL] = 2.// KERN_CRIT
  };
  // clang-format on
  static_assert(arraysize(kLogSeverityToKernelLogLevel) == android::base::FATAL + 1."Mismatch in size of kLogSeverityToKernelLogLevel and values in LogSeverity");
  / / static_assert is compiled assert that if the first parameter is true, then the compiler will not pass, here is the judgment kLogSeverityToKernelLogLevel array number is not greater than 7

  static int klog_fd = TEMP_FAILURE_RETRY(open("/dev/kmsg", O_WRONLY | O_CLOEXEC)); // Open the /dev/kmsg file
  if (klog_fd == - 1) return;

  int level = kLogSeverityToKernelLogLevel[severity];/ / according to the incoming log level to get Linux log level, namely kLogSeverityToKernelLogLevel corresponding subscript mapping

  // The kernel's printk buffer is only 1024 bytes.
  // TODO: should we automatically break up long lines into multiple lines?
  // Or we could log but with something like "..." at the end?
  char buf[1024];
  size_t size = snprintf(buf, sizeof(buf), "<%d>%s: %s\n", level, tag, msg);// Format log output
  if (size > sizeof(buf)) {
    size = snprintf(buf, sizeof(buf), "<%d>%s: %zu-byte message too long for printk\n",
                    level, tag, size);
  }

  iovec iov[1];
  iov[0].iov_base = buf;
  iov[0].iov_len = size;
  TEMP_FAILURE_RETRY(writev(klog_fd, iov, 1));// Write the log to /dev/kmsg
} 

Copy the code

3.3 DoFirstStageMount

Defined in the platform/system/core/init/init_first_stage CPP

The main function is to initialize and mount a specific device

bool DoFirstStageMount(a) {
    // Skips first stage mount if we're in recovery mode.
    if (IsRecoveryMode()) { // If it is in brush mode, skip mount directly
        LOG(INFO) << "First stage mount skipped (recovery mode)";
        return true;
    }

    // Firstly checks if device tree fstab entries are compatible.
    if(! is_android_dt_value_expected("fstab/compatible"."android,fstab")) { // If the value of fstab/compatible is not Android,fstab skips the mount
        LOG(INFO) << "First stage mount skipped (missing/incompatible fstab in device tree)";
        return true;
    }

    std: :unique_ptr<FirstStageMount> handle = FirstStageMount::Create();
    if(! handle) { LOG(ERROR) <<"Failed to create FirstStageMount";
        return false;
    }
    return handle->DoFirstStageMount(); // Initialize and mount specific devices
} 
Copy the code

3.4 handle – > DoFirstStageMount

Defined in the platform/system/core/init/init_first_stage CPP

Main role here is to parse the/proc/device – tree/firmware/android/fstab, then get “/ system”, “/ vendor”, “/ odm” three mount directory information

FirstStageMount::FirstStageMount()
    : need_dm_verity_(false), device_tree_fstab_(fs_mgr_read_fstab_dt(), fs_mgr_free_fstab) {
    if(! device_tree_fstab_) { LOG(ERROR) <<"Failed to read fstab from device tree";
        return;
    }
    for (auto mount_point : {"/system"."/vendor"."/odm"}) {
        fstab_rec* fstab_rec =
            fs_mgr_get_entry_for_mount_point(device_tree_fstab_.get(), mount_point); // The mount information is parsed
        if(fstab_rec ! =nullptr) {
            mount_fstab_recs_.push_back(fstab_rec);// The mount information is stored in an array}}}Copy the code

4. Enable SELinux security policy

SELinux stands for security-Enhanced Linux, It is an expanded mandatory access control Security module of Linux developed by NSA=The National Security Agency and Secure Computing Corporation (SCC). Under this access control system, a process can access only those files it needs for its task

if (is_first_stage) {

          ...
          
        //Avb stands for Android Verfied Boot, including Secure Boot, Verfying Boot and DM-verity.
        // The principle is to sign the binary file and authenticate it when the system is started to ensure that the system is running a valid binary image file.
        // The authentication ranges include bootloader, boot.img, and system.img
        SetInitAvbVersionInRecovery();// Initialize the avB version in brush mode, not brush mode directly skip

        // Set up SELinux, loading the SELinux policy.
        selinux_initialize(true);// Load SELinux policy, also known as security policy,
        

        // We're in the kernel domain, so re-exec init to transition to the init domain now
        // that the SELinux policy has been loaded.

        /* * SELinux policy has been loaded * 2. SELinux policy has been loaded * 2. The subsequent security_failure function calls panic to restart the system */
        if (restorecon("/init") = =- 1) { Using the restorecon command, you can restore the SELinux file attributes, that is, the security context of the file
            PLOG(ERROR) << "restorecon failed";
            security_failure(); // Restart the system}... }Copy the code

4.1 selinux_initialize

Defined in the platform/system/core/init/init. CPP

static void selinux_initialize(bool in_kernel_domain) {
    Timer t;

    selinux_callback cb;
    cb.func_log = selinux_klog_callback;
    selinux_set_callback(SELINUX_CB_LOG, cb); // Set selinux's log output handler
    cb.func_audit = audit_callback;
    selinux_set_callback(SELINUX_CB_AUDIT, cb);// Set selinux's record permission check handler

    if (in_kernel_domain) {// In_kernel_domain is true in phase 1 and false in phase 2
        LOG(INFO) << "Loading SELinux policy";
        if(! selinux_load_policy()) {// Load selinux security policy
            panic();
        }

        bool kernel_enforcing = (security_getenforce() == 1); // Get the current kernel working mode
        bool is_enforcing = selinux_is_enforcing(); // Get the working mode configuration
        if(kernel_enforcing ! = is_enforcing) {// If the current working mode is different from the configured one, change the working mode
            if (security_setenforce(is_enforcing)) {
                PLOG(ERROR) << "security_setenforce(%s) failed" << (is_enforcing ? "true" : "false"); security_failure(); }}if(! write_file("/sys/fs/selinux/checkreqprot"."0")) {
            security_failure();
        }

        // init's first stage can't set properties, so pass the time to the second stage.
        setenv("INIT_SELINUX_TOOK".std::to_string(t.duration_ms()).c_str(), 1);
    } else {
        selinux_init_all_handles(); // Initialize the handler in the second stage}}Copy the code

4.2 selinux_set_callback

Defined in the platform/external/selinux/libselinux/SRC/callbacks. C

Basically, you set the callback function based on the type,selinux_log,selinux_audit, those are all Pointers to the function

void selinux_set_callback(int type, union selinux_callback cb)
{
	switch (type) {
	case SELINUX_CB_LOG:
		selinux_log = cb.func_log;
		break;
	case SELINUX_CB_AUDIT:
		selinux_audit = cb.func_audit;
		break;
	case SELINUX_CB_VALIDATE:
		selinux_validate = cb.func_validate;
		break;
	case SELINUX_CB_SETENFORCE:
		selinux_netlink_setenforce = cb.func_setenforce;
		break;
	case SELINUX_CB_POLICYLOAD:
		selinux_netlink_policyload = cb.func_policyload;
		break; }}Copy the code

4.3 selinux_load_policy

Defined in the platform/system/core/init/init. CPP

Here to distinguish between the two kinds of circumstances, in both cases is to distinguish where the load security policy documents, the first from/vendor/etc/selinux/precompiled_sepolicy reading, the second is from/sepolicy Read, and they all end up calling the selinux_Android_load_policy_from_fd method

static bool selinux_load_policy(a) {
    return selinux_is_split_policy_device() ? selinux_load_split_policy()
                                            : selinux_load_monolithic_policy();
} 
Copy the code

4.4 selinux_android_load_policy_from_fd

Defined in the platform/external/selinux/libselinux/SRC/android/android. C

This function sets selinux_mnt to /sys/fs/selinux and then calls security_load_policy

int selinux_android_load_policy_from_fd(int fd, const char *description)
{
	int rc;
	struct stat sb;
	void *map = NULL;
	static int load_successful = 0;

	/* * Since updating policy at runtime has been abolished * we just check whether a policy has been loaded before * and return if this is the case. * There is no point in reloading policy. */
	if (load_successful){
	  selinux_log(SELINUX_WARNING, "SELinux: Attempted reload of SELinux policy! /n");
	  return 0;
	}

	set_selinuxmnt(SELINUXMNT); // the SELINUXMNT value is /sys/fs/selinux
	if (fstat(fd, &sb) < 0) {
		selinux_log(SELINUX_ERROR, "SELinux: Could not stat %s: %s\n",
				description, strerror(errno));
		return - 1;
	}
	/* * Mmap maps a file or other object into memory */
	map = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); 
	if (map == MAP_FAILED) {
		selinux_log(SELINUX_ERROR, "SELinux: Could not map %s: %s\n",
				description, strerror(errno));
		return - 1;
	}

	rc = security_load_policy(map, sb.st_size);
	if (rc < 0) {
		selinux_log(SELINUX_ERROR, "SELinux: Could not load policy: %s\n",
				strerror(errno));
		munmap(map, sb.st_size);
		return - 1;
	}

	munmap(map, sb.st_size);
	selinux_log(SELINUX_INFO, "SELinux: Loaded policy from %s\n", description);
	load_successful = 1;
	return 0;
} 
Copy the code

4.5 security_load_policy

Defined in the platform/external/selinux/libselinux/SRC/load_policy. C

Main effect of this function is to write data to the/sys/fs/selinux, before the data is actually looking for the policy file, from this we know that looks selinux_load_policy call so much code, Copy the policy file to /sys/fs/selinux

int security_load_policy(void *data, size_t len)
{
	char path[PATH_MAX];
	int fd, ret;

	if(! selinux_mnt) {// the selinux_mnt value is /sys/fs/selinux
		errno = ENOENT;
		return - 1;
	}

	snprintf(path, sizeof path, "%s/load", selinux_mnt);
	fd = open(path, O_RDWR); // open /sys/fs/selinux and write the value of data
	if (fd < 0)
		return - 1;

	ret = write(fd, data, len);
	close(fd);
	if (ret < 0)
		return - 1;
	return 0;
} 
Copy the code

4.6 security_setenforce

Defined in the platform/external/selinux/libselinux/SRC/setenforce. C

Selinux works in two modes:

  • Permissive: all operations are allowed (i.e., no MAC), but are logged if permissions are violated
  • enforcing, all operations are checked for permission. General user and user-debug mode

Both security_setenforce and security_getenforce operate the /sys/fs/selinux/enforce file. 0 indicates permissive. 1 indicates enforcing

int security_setenforce(int value)
{
	int fd, ret;
	char path[PATH_MAX];
	char buf[20];

	if(! selinux_mnt) { errno = ENOENT;return - 1;
	}

	snprintf(path, sizeof path, "%s/enforce", selinux_mnt);
	fd = open(path, O_RDWR); // Open the /sys/fs/selinux/enforce file
	if (fd < 0)
		return - 1;

	snprintf(buf, sizeof buf, "%d", value);
	ret = write(fd, buf, strlen(buf)); // Write the value of value to the file
	close(fd);
	if (ret < 0)
		return - 1;

	return 0;
} 
Copy the code

Five, start the preparation before the second stage

Set variables such as INIT_SECOND_STAGE and INIT_STARTED_AT to prepare for the second stage, and then call init’s main function again to start the user init process

if (is_first_stage) {

          ...

        setenv("INIT_SECOND_STAGE"."true".1);

        static constexpr uint32_t kNanosecondsPerMillisecond = 1e6;
        uint64_t start_ms = start_time.time_since_epoch().count() / kNanosecondsPerMillisecond;
        setenv("INIT_STARTED_AT", StringPrintf("%" PRIu64, start_ms).c_str(), 1);// Record the start time stamp of phase 2

        char* path = argv[0];
        char* args[] = { path, nullptr };
        execv(path, args); // Re-execute the main method to proceed to phase 2

        // execv() only returns if an error happened, in which case we
        // panic and never fall through this conditional.
        PLOG(ERROR) << "execv(\"" << path << "\") failed";
        security_failure();
    }
Copy the code

summary

The primary tasks of the init process in phase 1 are to mount partitions, create device nodes and some critical directories, initialize the log output system, and enable SELinux security policies

In the next article I’ll cover stage 2 of the init process