0

I'm moving some code from HPUX 11.11 to RHEL 7.5 and it includes a function that uses strchr. On HPUX it runs fine, and on RHEL there is a segmentation fault. I isolated the code and created the following simple test, with the subsequent results. It looks like HPUX strchr is returning an empty string rather than NULL when the character is not found. This is not what the man page says. I have found it might not be the strchr function but the difference in how HPUX handles NULL values, or a difference in compiler from cc to gcc. Does anyone actually know what's happening here? Why is the code not segfaulting on HPUX?

C Code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[]) {
    int i = 0;
    char* phrase = "Here is my phrase.";
    while (i < 5){
        phrase = strchr(phrase, ' ');
        ++phrase;

        ++i;
    }
    return 0;
}

HPUX makefile:

BIN=/path/to/bin/
CFLAGS=-c

#----------Targets----------#
default: $(BIN)strchrtest

#----------Objects----------#
OBJS = strchrtest.o

#----------------BUILD----------------#
$(BIN)strchrtest: $(OBJS) 
    cc -o $@ $(OBJS) 

strchrtest.o: strchrtest.c
    cc $(CFLAGS) strchrtest.c

RHEL makefile:

CC=gcc
BIN=/path/to/bin/
CFLAGS=-c -g -Wall

#----------Targets----------#
default: $(BIN)strchrtest

#----------Objects----------#
OBJS = strchrtest.o

#----------------BUILD----------------#
$(BIN)strchrtest: $(OBJS) 
    $(CC) -o $@ $(OBJS) 

strchrtest.o: strchrtest.c
    $(CC) $(CFLAGS) strchrtest.c

HPUX is just a successful result. No segmentation fault.

RHEL results:

(gdb) run
Starting program: ../strchrtest

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7b4c5f4 in __strchr_sse42 () from /lib64/libc.so.6

Copied form my comment below: The point is that the strchr function itself is causing the segfault when asked to find a character in the null pointer (that has been incremented), but on HPUX it doesn't. So it's either returning an empty string that is then passed strchr on the next loop, or strchr is handling the null pointer parameter differently by not segfaulting. Or I misunderstand what's happening.

Kyle
  • 97
  • 5
  • 1
    Depending on the way a process's address space is set up, a null pointer can *look like* an empty string. (And on such systems, it's easy to accidentally write code that depends on this.) – Steve Summit Nov 06 '19 at 18:06
  • C functions are not *required* to be "kind" when you pass a `NULL` pointer, but some are, for example MSVC's `printf` will output `(null)` but its `puts` will fail. – Weather Vane Nov 06 '19 at 18:06
  • 1
    Passing an invalid pointer to `strchr` or to `printf` is undefined behavior. (You increment a `NULL` pointer and pass this to `strchr`.) Maybe on HPUX `strchr` returns a `NULL` pointer in this case and maybe `printf` prints nothing when you supply an invalid pointer. You should [edit] your question and show the source code including the `printf`s. You should also print the pointer values with `printf("phrase=%p\n", phrase);` – Bodo Nov 06 '19 at 18:07
  • @Bodo I'll edit now, but the point is that the strchr function itself is causing the segfault when asked to find a character in the null pointer (that has been incremented), but on HPUX it doesn't. So it's either returning an empty string that is then passed strchr on the next loop, or strchr is handling the null pointer parameter differently by not segfaulting. Or I misunderstand what's happening. – Kyle Nov 06 '19 at 19:21
  • Check whether de-referencing NULL on HPUX causes a SIGSEGV? Long ago, some BSD's would map a readonly page of zeroes at NULL, so that "" and NULL were effectively synonyms. I thought it went out of style in the 1990s. If you are stuck with a source-base that relies on this, you might find mapping a zero page to address zero with MAP_FIXED might help you sleep at night. – mevets Nov 06 '19 at 19:28
  • @Kyle (ec) My guess (alluded to in my first comment, and echoed by mevets above) is that it has nothing to do with the way `strchr` or `printf` is written. My guess is that under HPUX, when you do the equivalent of `*(char *)0`, `*(char *)1`, etc., you get accesses to actual bytes in page 0 of your address space, not segmentation violations as you do under Linux. So `strchr` can search there until it finds a null byte, and `printf` can print there until it finds a null byte. (And if page 0 contains nothing but zeroes, both searches will always be satisfied quickly.) – Steve Summit Nov 06 '19 at 19:28
  • @Kyle And it's to answer this hypothesis that Bodo suggested printfing the pointer values *and* the substrings. – Steve Summit Nov 06 '19 at 19:30
  • @SteveSummit mevets This sounds like something that would be happening with this project. Thank you for suggesting it. I just adjusted the code to dereference null for an int and it came back as 0 on HPUX but segfaulted on RHEL. mevets I should do this then? Map "a zero page to address zero with MAP_FIXED" (No idea what this means but I don't mind figuring it out if it's what I need to do). Thanks. – Kyle Nov 06 '19 at 19:41
  • Although I know what @mevets meant by it, I have no idea how to go about actually mapping in a page like that under Linux, so in this situation I would have no choice but to chase down and fix all the null pointer access bugs. (The good news is that they're easy to find, because Linux points each one of them out to you with a nice, impossible-to-overlook Segmentation Fault.) – Steve Summit Nov 06 '19 at 19:47
  • @SteveSummit that would be a bummer, the program is huge... if that's the case is there anything I can add to the top of the c files that will mimic the behavior? – Kyle Nov 06 '19 at 19:52
  • @mevets is there a way to confirm on hpux if this is the case? I pretty much know it is based on behavior but is there an actual command to see if a page of zeroes has been mapped to null? – Kyle Nov 06 '19 at 20:09
  • I put it in answer because code. The root requirement is a hack, rather than fix the kernel to not jump to null occasionally, it inhibits the user from using this bit of her space. – mevets Nov 06 '19 at 20:23
  • @Kyle Your code contains undefined behavior (dereferencing a pointer of value 0 or 1). You should fix the code instead of trying to make this wrong code work on Linux. In general you should **check the return value of all function calls and handle all errors or special results**. In your example you should check for `NULL` result after `strchr`, e.g. add `if(!phrase) break;`. (This is a simple fix. I would rather re-write the loop.) – Bodo Nov 07 '19 at 09:13

1 Answers1

1

Make two files, r.c, main.c as such r.c:

int *r = 0;

main.c:

extern int *r;
int main(void) {
     return *r;
}

then cc main.c r.c; ./a.out if it doesn't sigsegv, your runtime is mapping a page of nulls for you. you could do it yourself - main.c:

#include <sys/mman.h>
int main(void) {
    p = mmap(0, 1, PROT_READ, MAP_ANON|MAP_PRIVATE|MAP_FIXED, -1, 0);
    if (p == MAP_FAILED) {
        perror("mmap");
        exit(1);
    }
     ....
}

but at least on ubuntu, you need to be root :(

mevets
  • 10,070
  • 1
  • 21
  • 33
  • I followed the instructions and it did not sigsegv. Is that second part something that needs to run once? Or every time the program runs? – Kyle Nov 06 '19 at 20:33
  • RHEL must have mmap; it is used for all sorts of things, including loading shared objects which are a little too common. – mevets Nov 06 '19 at 20:36