Linux reboot system call returns error despite correct arguments

Viewed 367

I am trying to power off my machine using the raw reboot system call. Here is my code:

#include <unistd.h>
#include <linux/reboot.h>
#include <stdio.h>
#include <errno.h>

int main(void) {
    int ret = reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_POWER_OFF, NULL);
    if (ret == -1) {
        printf("error: %d\n", errno);
    }
    return 0;
}

According to the man page, "Only the superuser may call reboot()". So I run the program as root and get the following output:

alex@laptop:~/code$ sudo ./a.out 
[sudo] password for alex: 
error: 22

What is error #22? That would be EINVAL, which according to the reboot man page signifies "Bad magic numbers or cmd". As I'm using the definitions provided by #include <linux/reboot.h>, I can't imagine the values are incorrect, but just in case, I also tried hardcoding the magic number values provided in the man page, to no avail (same error).

However, if I try glibc's wrapper function reboot(LINUX_REBOOT_CMD_POWER_OFF) provided by <sys/reboot.h>, it works fine (instantly shuts down machine).

There's no practical reason why I want to do this, just playing with syscall's and trying to make it work. If it helps, here are some details about the system I'm running this on:

Operating System: Ubuntu 20.04
Kernel: Linux version 5.4.0-53-generic
Compiler: gcc version 9.3.0

Let me know if any additional information might be useful!

3 Answers

I got this code from this link : Reboot with and without glibc

#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#ifdef NO_GLIBC
#include <linux/reboot.h>
#else
#include <sys/reboot.h>
#endif

#ifdef NO_GLIBC
#ifndef LINUX_REBOOT_MAGIC1
#define LINUX_REBOOT_MAGIC1 0xfee1dead
#endif

#ifndef LINUX_REBOOT_MAGIC2
#define LINUX_REBOOT_MAGIC2 0x28121969
#endif

#ifndef LINUX_REBOOT_MAGIC2A
#define LINUX_REBOOT_MAGIC2A 0x05121996
#endif

#ifndef LINUX_REBOOT_MAGIC2B
#define LINUX_REBOOT_MAGIC2B 0x16041998
#endif

#ifndef LINUX_REBOOT_MAGIC2C
#define LINUX_REBOOT_MAGIC2C 0x20112000
#endif
#endif /* NO_GLIBC */

#ifndef LINUX_REBOOT_CMD_RESTART
#define LINUX_REBOOT_CMD_RESTART 0x1234567
#endif

#ifndef LINUX_REBOOT_CMD_HALT
#define LINUX_REBOOT_CMD_HALT 0xcdef0123
#endif

#ifndef LINUX_REBOOT_CMD_POWER_OFF
#define LINUX_REBOOT_CMD_POWER_OFF 0x4321fedc
#endif

#ifndef LINUX_REBOOT_CMD_RESTART2
#define LINUX_REBOOT_CMD_RESTART2 0xa1b3c3d4
#endif

#ifndef LINUX_REBOOT_CMD_CAD_ON
#define LINUX_REBOOT_CMD_CAD_ON 0x89abcdef
#endif

#ifndef LINUX_REBOOT_CMD_CAD_OFF
#define LINUX_REBOOT_CMD_CAD_OFF 0x00000000
#endif

static void do_reboot(int command);

int
main(int argc, char *argv[]) {
  int reboot_command;

  if(argc == 1) {
    fprintf(stderr, "No command given.\n");
    fprintf(stderr, "Commands: RESTART, HALT, POWER_OFF, CAD_ON,
CAD_OFF.\n");
    return(2);
  } else if(strcasecmp(argv[1], "RESTART") == 0) {
    reboot_command = LINUX_REBOOT_CMD_RESTART;
  } else if(strcasecmp(argv[1], "HALT") == 0) {
    reboot_command = LINUX_REBOOT_CMD_HALT;
  } else if(strcasecmp(argv[1], "POWER_OFF") == 0) {
    reboot_command = LINUX_REBOOT_CMD_POWER_OFF;
  } else if(strcasecmp(argv[1], "RESTART2") == 0) {
    fprintf(stderr, "RESTART2 not supported. Try RESTART.\n");
    return(1);
  } else if(strcasecmp(argv[1], "CAD_ON") == 0) {
    reboot_command = LINUX_REBOOT_CMD_CAD_ON;
  } else if(strcasecmp(argv[1], "CAD_OFF") == 0) {
    reboot_command = LINUX_REBOOT_CMD_CAD_OFF;
  } else {
    fprintf(stderr, "%s not supported.\n", argv[1]);
    fprintf(stderr, "Commands: RESTART, HALT, POWER_OFF, CAD_ON,
CAD_OFF.\n");
    return(2);
  }

  do_reboot(reboot_command);

  // Not reached, unless command was CAD_ON or CAD_OFF.
  return(0);
}

static void
do_reboot(int cmd) {
  int reboot_status = 0;

  // Flush filesystem buffers before rebooting.
  sync();

  #ifdef NO_GLIBC
  // Old libc.
  reboot_status = reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, cmd,
NULL);
  #else
  // glibc uses a wrapper around the system call.
  reboot_status = reboot(cmd);
  #endif

  if(reboot_status == -1) {
    if(errno == EPERM) {
      fprintf(stderr, "Permission denied.  Are you root?\n");
    }
  }
}

From this code, we can understand that the format for reboot in oldlibc is :
int reboot(int magic, int magic2, unsigned int cmd, void *arg);
and the format for reboot in glibc is : int reboot(int cmd);

Reason for Error 22: This error implies "Invalid argument" (EINVAL). glibc expects single argument in reboot(int cmd) function call ( glibc uses a wrapper around the system call), but we are giving multiple arguments. That's the reason for the error.

Please see the function call in the above code :

#ifdef NO_GLIBC
  // Old libc.
  reboot_status = reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, cmd,
NULL);
  #else
  // glibc uses a wrapper around the system call.
  reboot_status = reboot(cmd);
  #endif

Although I was unable to make the int reboot(int magic, int magic2, int cmd, void *arg); syscall API work, I was eventually able to do this syscall using the lower level, general syscall API, like so:

#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>

int main(void) {
    syscall(169, 0xfee1dead, 672274793, 0x01234567);
    return 0;
}

Magic numbers can be interpreted using the man page at man reboot.2. And here is the equivalent x86_64 assembly:

section .text

global _start

_start:
  mov rax, 169
  mov rdi, 0fee1deadh
  mov rsi, 672274793
  mov rdx, 001234567h
  syscall

  ; The code below will never execute
  mov rax, 60
  mov rdi, 0
  syscall

My original goal was to execute this syscall from assembly, so although I'm still curious why the reboot function wouldn't work, I'm satisfied with what I've got.

So it looks like actual values are needed.

As it states on the below man page, you want to use the magic numbers as follows;

reboot( (int) 0xfee1dead, 672274793, 0x4321fedc ); // For Power Off as you've expressed above

https://man7.org/linux/man-pages/man2/reboot.2.html

Related