Discussion:
protecting NULL
(too old to reply)
muta...@gmail.com
2021-05-02 03:33:35 UTC
Permalink
It is very convenient for me to go:

*(char *)0 = 0;

to generate an exception, but that is not an exception
under PDOS/386, and I am wondering what is required
to change that.

I can't think of any reason why PDOS/386 needs to write
to low memory and disturb the real mode interrupt
vectors, but I'd be happy to start with just the first 4
bytes, if that is technically allowed.

Here is what I currently have:

typedef struct {
unsigned short limit;
unsigned short base15_0;
unsigned char base23_16;
unsigned char access;
unsigned char gran_limit;
unsigned char base31_24;
} descriptor;

static struct {
descriptor null_descriptor;
descriptor os_code;
descriptor os_data;
descriptor small_code;
descriptor small_data;
descriptor spawn_code;
descriptor spawn_data;
} descriptors = {
{0},
{ 0xffff, 0x0, 0xff, 0x9a, 0xcf, 0xff },
{ 0xffff, 0x0, 0xff, 0x92 /* not code, goes up, writable */, 0xcf, 0xff},
{ 0xffff, 0x0, /* scbase */ 0x00, 0x9a, 0x00, 0x00 },
{ 0xffff, 0x0, /* sdbase */ 0x00, 0x92, 0x00, 0x00 },
{ 0xffff, 0x0, 0x00, 0x9a, 0xcf, 0x00 },
{ 0xffff, 0x0, 0xff, 0x92, 0xcf, 0xff }
};

It looks to me like if I change:

{ 0xffff, 0x0, /* sdbase */ 0x00, 0x92, 0x00, 0x00 },

to:

{ 0xffff, 0x4, /* sdbase */ 0x00, 0x92, 0x00, 0x00 },

it might do what I want.

I can see the following potential problems:

1. A small number like 0x4 might not be allowed.
2. The size of 0xffff may no longer be acceptable and
need to be adjusted to at least 0xfffe
3. A reference to 0xb8000 may actually give me
0xb8004.

Anyone know what I need to do to prevent NULL pointer
assignment? I'm expecting to get an interrupt D if anyone
tries to write there. I don't mind if reads are allowed or not.

Thanks. Paul.
Rod Pemberton
2021-05-02 09:16:07 UTC
Permalink
On Sat, 1 May 2021 20:33:35 -0700 (PDT)
Post by ***@gmail.com
*(char *)0 = 0;
Casting any NON-ZERO value into a pointer in C is IB(*). I.e.,
if the C compiler allows the conversion, that's great, as it's really
needed for C to be useful, but the code will be non-compliant, and
non-portable.

(*)
(IB = Implementation-defined Behavior)
(UB = Undefined Behavior)

Casting a ZERO value into a pointer in C results in the NULL pointer,
which is NOT NECESSARILY the address value of zero, but which is
COMMONLY implemented as the address value zero. C only requires that
the NULL pointer not match the address of any other valid C object.
This means that the NULL pointer represented by symbolic "0" casted
into a pointer, may actually be a non-zero address. In other words,
for some C compilers, "*(char*)0=0;" will change a value in memory for
some location other than zero. This is valid. The C compiler must
recognize all language syntax which converts symbolic "0" representing
the NULL pointer into the correct NULL pointer address, which is
commonly the value zero, but not always.

Sample non-zero NULL implementation, from Douglas A. Gwyn, one of the
original ANSI C authors:
https://groups.google.com/g/comp.std.c/c/ez822gwxxYA/m/Jt94XH7AVacJ
Post by ***@gmail.com
to generate an exception, but that is not an exception
under PDOS/386, and I am wondering what is required
to change that.
Hardware.


1) This is really not a compiler or software issue.

E.g.,

a) mark the page as not present, if paging is enabled
b) mark the segment as not present in the descriptor
c) change the segment base address and limit


2) F*** it. Make it a compiler issue anyway.

E.g.,

a) force a load of a null selector for the NULL pointer.
Post by ***@gmail.com
I can't think of any reason why PDOS/386 needs to write
to low memory and disturb the real mode interrupt
vectors, but I'd be happy to start with just the first 4
bytes, if that is technically allowed.
PDOS/386 may not need to write to RM IVT.

However, DOS and DOS apps write to the RM IVT. DOS hooks a bunch of
vectors that are set by the BIOS. DOS also redirects some vectors for
different DOS utility programs. DPMI redirects some too. Sometimes
this is correctly done through the DOS calls to set vectors, but other
times, this is done directly. E.g., you can experiment with this by
using the 286 or later LIDT instruction to relocate the RM IVT
somewhere else. Programs which use DOS calls should correctly set the
RM IVT in the relocated IVT table. Whereas programs which directly
modify the IVT should modify the original IVT.
Post by ***@gmail.com
typedef struct {
unsigned short limit;
[...]
<snip>
Post by ***@gmail.com
1. A small number like 0x4 might not be allowed.
...
Post by ***@gmail.com
2. The size of 0xffff may no longer be acceptable and
need to be adjusted to at least 0xfffe
0xfffb?
Post by ***@gmail.com
3. A reference to 0xb8000 may actually give me
0xb8004.
DJGPP deals with #3 by using an offset that is added/subtracted to
obtain the correct address.
Post by ***@gmail.com
Anyone know what I need to do to prevent NULL pointer assignment?
a) don't code it.
b) use a non-zero NULL pointer address, e.g., 0xb8000
c) use hardware.
Post by ***@gmail.com
I'm expecting to get an interrupt D if anyone
tries to write there.
A GPF is caused for a reference to a null segment. I.e., you'd need to
load a null segment selector when the NULL pointer is being used. If
the NULL pointer is dereferenced, a GPF will occur for the access. See
the description for the MOV instruction:

"A null segment selector (values 0000-0003) can be loaded into the DS,
ES, FS, and GS registers without causing a protection exception.
However, any subsequent attempt to reference a segment whose
corresponding segment register is loaded with a null value causes a
general protection exception (#GP) and no memory reference occurs."
Post by ***@gmail.com
I don't mind if reads are allowed or not.
...
--
I'd love to answer that, but it would violate the TOS. Liberals can't
handle the truth, because the truth hurts.
JJ
2021-05-02 11:17:57 UTC
Permalink
Post by Rod Pemberton
1) This is really not a compiler or software issue.
E.g.,
a) mark the page as not present, if paging is enabled
b) mark the segment as not present in the descriptor
c) change the segment base address and limit
2) F*** it. Make it a compiler issue anyway.
I can't believe that was said by an OS developer. You do realize that you're
asking for a dictating compiler, don't you?
muta...@gmail.com
2021-05-02 21:53:11 UTC
Permalink
Post by ***@gmail.com
*(char *)0 = 0;
Casting any NON-ZERO value into a pointer in C is IB(*). I.e.,
if the C compiler allows the conversion, that's great, as it's really
needed for C to be useful, but the code will be non-compliant, and
non-portable.
Sure. I only do it temporarily when I'm debugging, to
force a crash and some sort of diagnosis.
However, DOS and DOS apps write to the RM IVT.
All of those things need to change for PDOS/386.

I don't mind if a 32-bit MSDOS app manipulates the
protected mode interrupt vectors. But they are not
located at location 0.
Post by ***@gmail.com
2. The size of 0xffff may no longer be acceptable and
need to be adjusted to at least 0xfffe
0xfffb?
I think the number is multiplied by 0x10000 so the
reasonable figure is 0xfffe.
Post by ***@gmail.com
3. A reference to 0xb8000 may actually give me
0xb8004.
DJGPP deals with #3 by using an offset that is added/subtracted to
obtain the correct address.
I don't want strange values in registers.
Post by ***@gmail.com
Anyone know what I need to do to prevent NULL pointer assignment?
a) don't code it.
Doing a divide by zero is a bit more effort. I guess
another option is to use a compiler-specific:

__asm("xxx");

where "xxx" is some sort of illegal instruction. Do you
know of something?
b) use a non-zero NULL pointer address, e.g., 0xb8000
Maybe this is the solution - write to 0xffffffff instead.
c) use hardware.
I guess one thing I could do is change Bochs to make
location 0 unwritable in protected mode.
Post by ***@gmail.com
I'm expecting to get an interrupt D if anyone
tries to write there.
A GPF is caused for a reference to a null segment. I.e., you'd need to
load a null segment selector when the NULL pointer is being used. If
It's not just a null segment - an attempt to write to
an address that doesn't belong to you gets the same
trap, right?

Another thing is that I'm only doing *(char *)0 = 0 in
the first place to get an exception and diagnosis.

Maybe I can instead simply have an INT 21H function
that causes an abort and core dump.

PosDump()? PosAbort()?

But that disturbs the registers. I want everything to
be pristine.

BFN. Paul.
muta...@gmail.com
2021-05-03 14:56:35 UTC
Permalink
Post by ***@gmail.com
Maybe I can instead simply have an INT 21H function
that causes an abort and core dump.
PosDump()? PosAbort()?
But that disturbs the registers. I want everything to
be pristine.
I think this is good enough for now, and maybe even
better than I was previously doing:

__asm("INT $0");

BFN. Paul.

Continue reading on narkive:
Loading...