0

I am attempting to create a custom system call that inputs an (long) array and outputs some basic information about this array for a class assignment.

I am having some trouble passing the appropriate arguments into the system call.

Below is the test program I am using to debug this system call:

#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include "array_stats.h"

#define _ARRAY_STATS_ 341 // for a 64 bit system

int main () {

  struct array_stats * stats;

  long ary[3];
  ary[0] = 1;
  ary[1] = 2;
  ary[2] = 3;

  long s = 3;

  printf("\nDiving to kernel level\n\n");

  // Calling array_stats syscall
  int result = syscall(_ARRAY_STATS_, &stats, ary, s);

  printf("\nRising to user level w/ result = %d\n\n", result);

  return 0;
}

Here is the array_stats system call, I have inserted a bunch of printk() statements for debugging purposes:

#include <linux/kernel.h>
#include <asm/errno.h>
#include <linux/uaccess.h>
#include "array_stats.h"

asmlinkage long sys_array_stats (struct array_stats *stats, long data[], long size) {
  printk("1. %s\n", "System call started");

  long loc_max;
  long loc_min;
  long loc_sum;
  long local_data[size];
  int i = 1;
  int result = 0;

  printk("2. %s\n", "Variables created");
  printk("Size %lu\n", size);
  printk("data[0] = %lu\n", data[0]);
  printk("data[1] = %lu\n", data[1]);
  printk("data[2] = %lu\n", data[2]);

  if (size <= 0) return -EINVAL;

  printk("3. %s\n", "Size validated");

  result = copy_from_user (local_data, data, size * sizeof * data);

  if (!result) return EFAULT;

  loc_max = local_data[0];
  loc_min = local_data[0];
  loc_sum = local_data[0];

  for (; i < loc_sum; i++) {
    loc_sum += local_data[i];
    if (local_data[i] < loc_min) loc_min = local_data[i];
    if (local_data[i] > loc_max) loc_max = local_data[i];
  }

  stats -> min = loc_min;
  stats -> max = loc_max;
  stats -> sum = loc_sum;

  printk("Min: %lu\n", loc_min);
  printk("Max: %lu\n", loc_max);
  printk("Sum: %lu\n", loc_sum);

  return result;
}

The running of the test program results in the following:

root@debian-amd64:~# ./array_stats_test 

Diving to kernel level

[   92.735994] 1. System call started
[   92.737851] 2. Variables created
[   92.738037] Size 0
[   92.738155] data[0] = 0
[   92.738417] data[1] = 6955032
[   92.738565] data[2] = 0

Rising to user level w/ result = -1

root@debian-amd64:~# 

As you can see, the arguments aren't being passed in properly. What am I doing wrong?

Thanks in advance!

Guy Avraham
  • 3,482
  • 3
  • 38
  • 50
Wahotaho
  • 9
  • 3
  • According to [Linux Programmer's Manual - SYSCALL(2)](http://man7.org/linux/man-pages/man2/syscall.2.html), `syscall()` has signature `long syscall(long number, ...);`. This in mind, `stats` is probably passed wrong. Either it shouldn't be declared as pointer or you shouldn't pass the address of `stats` to `syscall()`. At second glance, I believe the error is `struct array_stats * stats;` which has to be `struct array_stats stats;` because I couldn't find any kind of allocation in `sys_array_stats()` for `stats`. May be, you should have debugged it first without calling via `syscall()`... – Scheff's Cat Jun 03 '18 at 12:10
  • Just want to chime in here and say that there is a serious vulnerability in this code. If you make stats point at somewhere in the kernel, you can cause the kernel to overwrite important kernel data, and even change your user to root! You should use *copy_to_user* as well as copy_from_user. Look at gettimeofday: https://elixir.bootlin.com/linux/latest/source/kernel/time/time.c#L143 – Muricula Nov 05 '18 at 23:16

0 Answers0