NetBSD-Bugs archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

lib/59559: libc needs namespace protection for RENAME'd symbols



>Number:         59559
>Category:       lib
>Synopsis:       libc needs namespace protection for RENAME'd symbols
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    lib-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Sun Jul 27 13:45:00 +0000 2025
>Originator:     Robert Elz
>Release:        All of them
>Organization:
>Environment:
Irrelevant
>Description:
	Note this is an offshoot of PR toolchain/59549 so you can look
	at that one to get the history - just ignore everything about
	both gdb and ctype(3) (the two subjects of that PR) as none of
	that is relevant here, and concentrate only on the reason that
	gdb was needed in the first place for that PR.

	Also see src/lib/libc/README for information on how the libc
	namespace & __RENAME() functionality is intended to work.

	And last of the preamble here, note that a lot of this is just
	my impressions, and might all be wrong, so if you know better,
	please explain the errors.

	What that README doesn't mention (and should) is that almost
	every libc global symbol which is referenced (as distinct from
	just defined) inside libc everywhere, needs to undergo the
	namespace dance, as described there for "NetBSD-specific
	nonstandard extensions" (even when they aren't) as otherwise
	a call in libc to what should be a standard function can be
	overridden by an application which doesn't use that symbol,
	doesn't include any header which defines it, and hence is
	permitted to redefine the symbol however it likes.

	But that's not the point of this PR, just a side comment.

	It is the third type of libc function (in the README, in the
	section "Old versions of library routines") which is the
	issue here.

	I will briefly summarise the example given there so I can
	expand on it more:

	There once was a function (well there still is)
		time_t  time(time_t *);
	but once upon a time, time_t was int (aka int32_t)
	making that function effectively:
		int32_t time(int32_t *);
	but the definition of time_t was changed, so now
	we actually need
		int64_t time(int64_t *);

	In olden timed we had
		_time() (from namespace.h) for the code
		time	(as a weak alias in libc).

	With that, any really old code that called time() from
	libc actually ended up calling _time() - the 32 bit
	version of that interface - yet if an application doesn't
	care about that interface, doesn't include <time.h> nor
	any other header which defines it, it can define its own
	global "time" (as a global variable, or a function),
	which it can use, while anything inside libc calls _time()
	and all is good - this is how things are supposed to work.

	(And note, that up to that point, the signature of time()
	had never [within the meaning of "never" here] changed,
	so according to the README, the _time() variant from
	namespace wouldn't be needed - but it is, and was, and
	was all done correctly, it is just the README that is
	lacking here.)

	Then we changed time_t, and as README says, libc now
	provides

        _time           global (implementing the old signature)
        time            weak alias for _time
        __time50        global (implementing the new signature)

	which all looks good, both _time and __time50 (for the "50"
	see the README, that's not especially relevant here) are
	_* symbols, which applications are not supposed to use,
	those are reserved to the implementation, so no application
	should be defining a global symbol __time50.

	Or you'd think.

	But the application still calls "time(...)", and
	compiler/linker magic make new calls turn into calls to
	__time50() (that's OK, that's the implementation doing
	that, so use of the _* symbol is permitted).   That's
	what makes all of this work for your normal application.

	But the application can also redefine time to be something
	else - except this now turns into a bad example to use,
	as time() is a standard function, defined in <time.h> and
	if the application has included <time.h> it is not supposed
	to redefine any symbols that header makes available.

	So let's switch to the other case "NetBSD-specific nonstandard
	extensions" - those things can be subject to signature changes
	as much as anything else, and for this use the example from the
	original PR (what caused all the problems) gmtime_r -- this is
	a POSIX standard function, but not a C standard function (which
	kind of makes it a weird special case, but still).

	That one got internally renamed (to __gmtime_r50) at the same
	time as time() was renamed above.

	But as gmtime_r isn't a standard C function, an application
	which wants to use it might very easily want to define a version
	of it, just in case the implementation does not provide that
	function (which as it happens, some do not, I believe, despite
	it being required by POSIX).

	As the function is intended to implement the (POSIX) standard
	gmtime_r function, it is reasonable to get its declaration from
	<time.h> which is where POSIX says it should be (though that is
	probably fairly stupid, as if the declaration is there, and
	visible, the implementation should be available as well).

	Anyway, if the application does define a gmtime_r() function,
	the renaming magic that accompanies the declaration of that
	function (as the original gmtime_r is the 32 bit version, and
	no new code wants that any more) applies to the definition of
	the function (just as it does - as the README explains - to the
	implementation inside libc).

	That is, by providing a mktime_r function definition, what the
	application has actually done (with implementation assistance)
	is to define a function called __mktime_r50().   That would be
	OK, except internally libc calls mktime_r (it is used to implement
	mktime) so now we have the libc internals calling the application's
	function - from which good results should not be expected (and
	indeed, they were not - the implementation has mktime() calling
	mktime_r() - the application has mktime_r() calling mktime(), using
	the libc version of that, which is of course, simply infinite
	recursion, hence the application crashed).

	We could avoid that by treating all RENAME'd symbols (which always
	rename into implementation namespace of course) as if they were
	global symbols in the application namespace, and doing the namespace
	dance for them as well -- meaning that in this case, __gmtime_r50
	would be ___gmtime_r50, and that's what would be called inside libc,
	and for external callers, there'd be a weak alias, which would map
	__gmtime_r50 into ___gmtime_r50 for any application which does not
	define its own __gmtime_r50 (ie: gmtime_r).

	That is, we could, but should we?   The __RENAME() is only visible
	in the header along with the declaration of the function being
	renamed, and if that is there, and the application has made it
	visible, do the application should not be redefining the function.
	So we could just leave things alone, and punt any issues caused
	by this back as application bugs (which really they are).  If the
	application in this case had simply defined mktime_r without including
	<time.h> if that were possible (in this case, I believe not), or or
	explicitly requested strict Cxx (doesn't matter which version)
	then it would not have encountered the __RENAME() and it all would
	have just worked.

	[Note: for the application in question doing all this was probably
	insane anyway, as its gmtime_r() implementation was decidedly inferior
	to the libc version, and in particular, was not thread safe, in an
	application which, I believe, uses threads.]

>How-To-Repeat:
	See PR toolchain/59549    But don't bother trying to make it happen.

>Fix:
	We could add a bunch more to src/lib/libc/include/namespace.h
	but should we?



Home | Main Index | Thread Index | Old Index