@@ -199,7 +199,6 @@ static void pctnorm_load_config(void) {
199199}
200200static inline void ensure_config (void ) { (void )pthread_once (& config_once , pctnorm_load_config ); }
201201
202-
203202/* Lazy re-resolve TLS symbols in case libssl/gnutls were dlopen()'d later. */
204203#if PCT_WANT_TLS
205204static inline void resolve_tls_if_needed (void ) {
@@ -242,47 +241,81 @@ static void collect_triplet_cases_from_segment(const char *s) {
242241 }
243242}
244243
244+ /* Bounded substring search: returns pointer to first occurrence of needle
245+ in haystack (bounded by hlen), or NULL if not found. */
246+ static inline const char * bounded_strstr_n (const char * hay , size_t hlen ,
247+ const char * needle , size_t nlen )
248+ {
249+ if (!hay || !needle || nlen == 0 ) return hay ;
250+ if (hlen < nlen ) return NULL ;
251+
252+ #if defined(__GLIBC__ ) || defined(__linux__ ) || defined(__APPLE__ ) || \
253+ defined(__FreeBSD__ ) || defined(__NetBSD__ ) || defined(__OpenBSD__ )
254+ return (const char * )memmem (hay , hlen , needle , nlen );
255+ #else
256+ const char * end = hay + (hlen - nlen ) + 1 ;
257+ for (const char * p = hay ; p < end ; ++ p ) {
258+ if (p [0 ] == needle [0 ] && memcmp (p , needle , nlen ) == 0 )
259+ return p ;
260+ }
261+ return NULL ;
262+ #endif
263+ }
264+
245265/* crude but safe parse of first http(s) URL in /proc/self/cmdline and capture path+query */
246266static void init_argv_triplet_map (void ) {
247267 int fd = open ("/proc/self/cmdline" , O_RDONLY );
248268 if (fd < 0 ) return ;
269+
249270 char buf [8192 ];
250271 ssize_t n = read (fd , buf , sizeof buf - 1 );
251272 close (fd );
252273 if (n <= 0 ) return ;
274+
275+ /* Add a sentinel NUL so plain C-string ops on copies are safe.
276+ The valid range is [buf, buf+n). endbuf does NOT include extra NUL. */
253277 buf [n ] = '\0' ;
278+ char * p = buf ;
279+ char * endbuf = buf + n ;
254280
255281 /* /proc/self/cmdline is NUL-separated argv */
256- char * p = buf ;
257- while (p < buf + n ) {
258- size_t len = strlen (p );
282+ while (p < endbuf ) {
283+ size_t remain = (size_t )(endbuf - p );
284+
285+ /* Find next NUL within bounds */
286+ const char * nul = memchr (p , '\0' , remain );
287+ if (!nul ) break ;
288+ size_t len = (size_t )(nul - p );
259289 if (len == 0 ) break ;
260290
261291 if (!strncasecmp (p , "http://" , 7 ) || !strncasecmp (p , "https://" , 8 )) {
262292 const char * u = p ;
263- const char * path = strstr (u , "://" );
293+ const char * path = bounded_strstr_n (u , len , "://" , 3 );
264294 if (path ) {
265- path += 3 ; /* skip "://" */
295+ path += 3 ;
296+
266297 /* skip authority host[:port] */
267298 while (* path && * path != '/' && * path != '?' && * path != '#' ) path ++ ;
299+
268300 /* capture path + query (fragment not transmitted) */
269301 if (* path == '/' || * path == '?' ) {
270- /* terminate at '#' or NUL */
271302 const char * end = path ;
272303 while (* end && * end != '#' ) end ++ ;
273- char * tmp = (char * )malloc ((size_t )(end - path + 1 ));
304+
305+ size_t seglen = (size_t )(end - path );
306+ char * tmp = (char * )malloc (seglen + 1 );
274307 if (tmp ) {
275- memcpy (tmp , path , ( size_t )( end - path ) );
276- tmp [end - path ] = '\0' ;
308+ memcpy (tmp , path , seglen );
309+ tmp [seglen ] = '\0' ;
277310 collect_triplet_cases_from_segment (tmp );
278311 free (tmp );
279312 }
280313 }
281314 }
282- break ; /* only first URL */
315+ break ;
283316 }
284317
285- p += len + 1 ;
318+ p = ( char * ) nul + 1 ;
286319 }
287320}
288321
0 commit comments