svn commit: r257363 - head/usr.bin/xinstall

Bruce Evans brde at optusnet.com.au
Wed Oct 30 00:01:50 UTC 2013


On Tue, 29 Oct 2013, Sean Bruno wrote:

> Log:
>  Queisce sign errors by using unsigned char * and casting MAP_FAILED as unsigned
>  char *

This increases the type errors.

> Modified: head/usr.bin/xinstall/xinstall.c
> ==============================================================================
> --- head/usr.bin/xinstall/xinstall.c	Tue Oct 29 20:36:04 2013	(r257362)
> +++ head/usr.bin/xinstall/xinstall.c	Tue Oct 29 20:38:00 2013	(r257363)
> @@ -1002,7 +1002,7 @@ compare(int from_fd, const char *from_na
> 	int to_fd, const char *to_name __unused, size_t to_len,
> 	char **dresp)
> {
> -	char *p, *q;
> +	unsigned char *p, *q;
> 	int rv;
> 	int done_compare;
> 	DIGEST_CTX ctx;

Using plain char is fragile and often broken for the things that it
is used for in C.  I happen to have been recently writing too much
about this in the comp.std.c newsgroup.  Using plain char is less
fragile in POSIX than in C, since POSIX-2001 requires signed char to
be int8_t so it cannot be 1's complement or signed magnitude, or have
padding bits.

No change should be made here.  Full changes to unsigned char would
require poisoning almost all char declarations by adding 'unsigned',
starting with strcpy().  Fixing strcpy()'s API was too hard for the C
standard in 1990 when C was less than half as old as now.  strcpy()
is only required to use unsigned char's internally.  This wasn't
explicit until C99.

> @@ -1018,11 +1018,11 @@ compare(int from_fd, const char *from_na
> 		if (trymmap(from_fd) && trymmap(to_fd)) {
> 			p = mmap(NULL, from_len, PROT_READ, MAP_SHARED,
> 			    from_fd, (off_t)0);
> -			if (p == (char *)MAP_FAILED)
> +			if (p == (unsigned char *)MAP_FAILED)
> 				goto out;
> 			q = mmap(NULL, from_len, PROT_READ, MAP_SHARED,
> 			    to_fd, (off_t)0);
> -			if (q == (char *)MAP_FAILED) {
> +			if (q == (unsigned char *)MAP_FAILED) {
> 				munmap(p, from_len);
> 				goto out;
> 			}

The casts of MAP_FAILED should be removed.  They have no effect except to
break some cases.  Here they mainly caused a type mismatch when the type
of the left hand operand was changed.  This depends on MAP_FAILED having
the correct type (void *).

The fallback definition of MAP_FAILED in xinstall.c should also be removed.
It is compatibility cruft that was last needed in FreeBSD in 1996.  FreeBSD's
MAP_FAILED had the wrong type (caddr_t) until 1997.

> @@ -1036,7 +1036,7 @@ compare(int from_fd, const char *from_na
> 		}
> 	out:
> 		if (!done_compare) {
> -			char buf1[MAXBSIZE];
> +			unsigned char buf1[MAXBSIZE];
> 			char buf2[MAXBSIZE];
> 			int n1, n2;
>

It's bizarre to use unsigned chars for one buffer and still use plain
chars for the other.

I now see what this change does.  digest_update() takes an unsigned
char * pointer.  This makes it hard to use with normal buffers containing
char, like strcpy() would be if its pointers were unsigned char *.

The correct fix is to break digest_update()'s API like strcpy()'s, or
to cast the pointer arg to unsigned char * whenever it is called
starting with a char * arg.  APIs are hard to change, so the latter
fix is normally used.  Here the API is local, so it is easy to change.
digest_update() calls SHA*_Update(), and no further changes are needed
for that since it takes a void * arg and converts internally to the
correct type, which can only be unsigned char *.

Some applications are too smart and use unsigned char buffers.  They
have essentially the same problem in the opposite direction with using
functions like strcpy().  I used to think that it was a bug to cast
pointer args in str*() calls, since it either has no effect or hides
type mismatch, and type mismatch may cause inconsistent comparisons.
(Note that even strcpy() needs to do comparisons for finding the null
terminator and must use unsigned char to get these right.  However,
the bug is just in the API -- both the caller and the callee should
be using unsigned char for everything.

The new type mismatch between buf1 and buf2 asks for identical files
to often compare unequal, since bytes that have their "sign" bit set
become negative in buf2 but stay positive in buf1.  (Files contain
bytes and bytes don't really have a sign bit, but accessing them via
buf2 corrupts them iff chars are signed.)  However, there is no problem
in practice, since buf1[n] and buf2[n] are never compared directly --
all comparisons are done by calling memcmp(), and it compares bytes.

> @@ -1146,7 +1146,8 @@ copy(int from_fd, const char *from_name,
> {
> 	int nr, nw;
> 	int serrno;
> -	char *p, buf[MAXBSIZE];
> +	unsigned char *p;
> +	unsigned char buf[MAXBSIZE];
> 	int done_copy;
> 	DIGEST_CTX ctx;
>

Don't change.

> @@ -1166,7 +1167,7 @@ copy(int from_fd, const char *from_name,
> 	done_copy = 0;
> 	if (size <= 8 * 1048576 && trymmap(from_fd) &&
> 	    (p = mmap(NULL, (size_t)size, PROT_READ, MAP_SHARED,
> -		    from_fd, (off_t)0)) != (char *)MAP_FAILED) {
> +		    from_fd, (off_t)0)) != (unsigned char *)MAP_FAILED) {
> 		nw = write(to_fd, p, size);
> 		if (nw != size) {
> 			serrno = errno;
>

Remove bogus cast, and fix indentation.

Bruce


More information about the svn-src-all mailing list