0

I have the following assignment for an online class and was wondering if anyone was familiar with paranoid arrays, as it's very difficult to get help for this specific class.

Your paranoid array will expose a very simple interface provided in parray.h. You can assume the parray will not be free’d. The parray new call creates an array of a set-number of entries (count argument) of a fixed size (size argument). Internally, the parray will not arrange the elements consecutively in memory, so the parray entry function returns a pointer to a given entry (specified by argument index). In order to trigger a segfault upon overflow within an element, you should use guard pages. A guard page’s main purpose is to trigger segfaults when it is accessed. Thus, pagetable read, write, and execute permissions on a guard table are disabled, so any access to the page will trigger the fault. When a guard page is placed immediately after a buffer or data structure, any buffer overflow bugs affecting that piece of memory will hit the guard page, triggering an instant segfault

Every entry in your array should be bounded by guard pages on each side. For example, an array with 10 entries should use 11 guard pages: one before the first entry, one after the last entry, and nine in between consecutive entries.

My parray_new and parray_entry call is as follows:

 typedef char byte;

 parray_t* parray_new(int size, int count)
 {
   struct parray* p = NULL;
   // TODO: Allocate and return parray
   // Add guard pages first at this time
   int pagesize = getpagesize();
 p->size = (size * count) + (pagesize * count) + pagesize;
 p->array = malloc(p->size + pagesize - 1);
 if(posix_memalign(&p->array, p->size, count))
 {
    exit(0);
 }

 return p;
 }

 void* parray_entry(struct parray* p, int index)
 {
   //int pagesize = getpagesize();
   byte* entry = NULL;
   // TODO: compute correct entry
   if (mprotect(&p->array, p->size-1, PROT_READ))
  {
         exit(0);
  }
   if (mprotect(&p->array, p->size, PROT_WRITE))
  {
     exit(0);
  }
   entry = (void*)(p->array + index); 
   return entry;
 }

I also have the following handler:

 static void handler(int sig, siginfo_t *si, void* unused)
 {
 // TODO: Use fprintf or perror to print 
 // a message indicating a segmentation fault
 // happened and provide the memory address
 // where the fault happened
 fprintf(stderr, "Segmentation Fault\n k = %d, %p\n", sig, si >si_addr);
 }

Finally, the main method:

 int main(void)
 {
    struct sigaction sa;
    /*
    * TODO: Overwrite the signal handler for 
    * SIGSEGV
    */
    memset(&sa, '\0', sizeof(sa));
    sa.sa_flags = SA_SIGINFO;
    sa.sa_sigaction = handler;
    if (sigaction(SIGSEGV, &sa, NULL) == -1)
    {
       perror("sigaction");
       exit(EXIT_FAILURE);
    }   
 }

There's also several tests to run in the main method, but I've left those out because I encounter an error before I even reach them. What happens is, the handler prints forever (Segmentation fault, k = 11, 0x8). I do not know the significance of 11 or 0x8, but it does not stop printing that sequence until I force it.

Any help would be greatly appreciated, and I apologize for the length of this post. Thanks

Edit: from what I can see, the handler continues to print. It's not so much that I'm getting a seg fault (I might be), but whatever I put in the handler it continues to print. Also if I change it to perror it does the same. What can I do to allow the program to continue after the handler?

nhlyoung
  • 67
  • 2
  • 8
  • 1
    See [How to avoid calling `printf()` in a signal handler?](https://stackoverflow.com/questions/16891019/how-to-avoid-using-printf-in-a-signal-handler/) for information about why you should not write your signal handler as you did. – Jonathan Leffler Apr 03 '19 at 00:14
  • 1
    Signal 11 is often `SIGSEGV` (it is on a Mac running macOS, or on Linux). – Jonathan Leffler Apr 03 '19 at 00:15
  • 1
    If the code in `main()` as shown reproduces the core dump, we don't need to know anything about paranoid arrays because nothing in `main()` invokes any of the paranoid array code (you've not provided an MCVE ([MCVE]); it is lacking in the M = minimal department). If the code in `main()` as shown does not reproduce the core dump, then you've not provided an MCVE (it is lacking in the C = completeness department). – Jonathan Leffler Apr 03 '19 at 00:19
  • 3
    Returning from handling a SIGSEGV can lead to the same fault occurring again, because you've not fixed the cause, so an infinite loop is perfectly plausible. POSIX says ([Signal concepts](http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04)): _The behavior of a process is undefined after it returns normally from a signal-catching function for a SIGBUS, SIGFPE, SIGILL, or SIGSEGV signal that was not generated by `kill()`, `sigqueue()`, or `raise()`._ – Jonathan Leffler Apr 03 '19 at 00:19
  • Why do you want to handle SIGSEGV at all? It doesn't seem to be part of the assignment. The default behavior on SIGSEGV is to crash your program and (depending on `ulimit` and other settings) create a core dump, which is likely desirable when your program has encountered a serious bug. – Nate Eldredge Apr 03 '19 at 04:07
  • AFAIK, the only halfway portable way to recover after SIGSEGV is with `setjmp / longjmp`. Using these properly is a whole other challenge, though. – Nate Eldredge Apr 03 '19 at 04:08
  • If you return normally from the signal handler, your code can (and, it seems, does) trigger the same fault again, reentering the signal handler again. You cannot reliably return from a signal handler when the fault is a SIGSEGV caused by a program flaw. You can return abnormally via `siglongjmp()` (or `longjmp()` perhaps) if you've set up the appropriate environment in which that is feasible, but that is painfully hard to get right (and doubly painfully hard to get leak-proof). It would be better to exit — or simply not attempt to handle SIGSEGV and leave the default behaviour in effect. – Jonathan Leffler Apr 03 '19 at 06:51

0 Answers0