~ chicken-core (chicken-5) d3f97228a1403e69f894cddeb05ef38067457976


commit d3f97228a1403e69f894cddeb05ef38067457976
Author:     Peter Bex <peter@more-magic.net>
AuthorDate: Fri Mar 24 10:42:49 2023 +0100
Commit:     felix <felix@call-with-current-continuation.org>
CommitDate: Sat Apr 8 11:39:40 2023 +0200

    Stop run-time option processing after "-:" or the first non-runtime option
    
    This means that runtime options after non-runtime options will no
    longer be processed.  Programs using command-line-arguments will
    see all options starting at the first non-runtime option, even if
    later options start with "-:".
    
    Processing can also be stopped explicitly with a single "-:" option,
    which will not be seen by programs using command-line-arguments.
    Every argument following this "-:" will be seen, even if they start
    with "-:".
    
    Signed-off-by: felix <felix@call-with-current-continuation.org>

diff --git a/NEWS b/NEWS
index 23661fc2..6ecbbad2 100644
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,10 @@
     to avoid arbitrary OS command injection during egg installation,
     reported by Vasilij Schneidermann who also provided the necessary
     patches to mitigate this problem.
+  - The runtime option "-:b" has been removed, as it was deemed too
+    insecure to be able to drop to a REPL from the CLI of any program.
+  - Runtime option processing has been hardened: processing now stops on
+    the first non-runtime option or after "-:", whichever comes first.
 
 - Core libraries
   - Added "locative-index", kindly contributed by John Croisant.
diff --git a/library.scm b/library.scm
index 827666d5..819dacfe 100644
--- a/library.scm
+++ b/library.scm
@@ -6024,17 +6024,23 @@ static C_word C_fcall C_setenv(C_word x, C_word y) {
 
 (define command-line-arguments
   (make-parameter
-   (let ([args (argv)])
+   (let ((args (argv)))
      (if (pair? args)
-	 (let loop ([args (##sys#slot args 1)])
+	 (let loop ((args (##sys#slot args 1)))	; Skip over program name (argv[0])
 	   (if (null? args)
 	       '()
-	       (let ([arg (##sys#slot args 0)]
-		     [r (##sys#slot args 1)] )
-		 (if (and (fx>= (##sys#size arg) 3)
-			  (string=? "-:" (##sys#substring arg 0 2)))
-		     (loop r)
-		     (cons arg (loop r)) ) ) ) )
+	       (let ((arg (##sys#slot args 0))
+		     (rest (##sys#slot args 1)) )
+		 (cond
+		  ((string=? "-:" arg)	; Consume first "empty" runtime options list, return rest
+		   rest)
+
+		  ((and (fx>= (##sys#size arg) 3)
+			(string=? "-:" (##sys#substring arg 0 2)))
+		   (loop rest))
+
+		  ;; First non-runtime option and everything following it is returned as-is
+		  (else args) ) ) ) )
 	 args) )
    (lambda (x)
      (##sys#check-list x 'command-line-arguments)
diff --git a/manual/Module (chicken process-context) b/manual/Module (chicken process-context)
index 5299905e..87165187 100644
--- a/manual/Module (chicken process-context)	
+++ b/manual/Module (chicken process-context)	
@@ -13,7 +13,7 @@ This module provides access to the current process context.
 
 Returns two values: an integer and a foreign-pointer object
 representing the {{argc}} and {{argv}} arguments passed to the current
-process.
+process.  See also {{argv}} below.
 
 ==== argv
 
@@ -24,13 +24,27 @@ the list is a string containing the name of the executing program. The
 other items are the arguments passed to the application. It depends on
 the host-shell whether arguments are expanded ('globbed') or not.
 
+NOTE: This is the "raw" unprocessed argument list, including runtime
+options (starting with {{-:}}) which are consumed by the runtime
+library.
+
 ==== command-line-arguments
 
 <parameter>(command-line-arguments)</parameter>
 
-Contains the list of arguments passed to this program, with the name of
-the program and any runtime options (all options starting with {{-:}})
-removed.
+Contains the list of arguments passed to this program.
+
+This ''excludes'' the name of the program, as well as any runtime
+options (options starting with {{-:}}) up until the first empty
+runtime option (just {{"-:"}}) or non-runtime option, whichever
+comes first.
+
+In other words, this method returns every option ''after'' the first
+list of unbroken runtime options, which are all skipped.  If an empty
+runtime option is present, that is the last of this list of unbroken
+runtime options and everything after it is returned by this method.
+If a non-runtime option is present, that also breaks up the runtime
+options and this method returns that and every following option.
 
 ==== executable-pathname
 
diff --git a/manual/Using the compiler b/manual/Using the compiler
index 30e5ed2a..e9eb3672 100644
--- a/manual/Using the compiler	
+++ b/manual/Using the compiler	
@@ -220,10 +220,17 @@ to see a list of all supported options and short aliases to basic options.
 === Runtime options
 
 After successful compilation a C source file is generated and can be
-compiled with a C compiler. Executables generated with CHICKEN (and the
-compiler itself) accept a small set of runtime options. These are filtered out
-by the startup code and will not be contained in the result of 
-{{(command-line-arguments)}}.
+compiled with a C compiler. Executables generated with CHICKEN (and
+the compiler itself) accept a small set of runtime options.
+Processing of these options stops at the first non-runtime option, or
+''after'' the empty runtime option (just "{{-:}}"), whichever comes
+first.
+
+In other words, the program sees every option after the first list of
+unbroken runtime options, which are all consumed.  If an empty runtime
+option is present, that is the last of this list of unbroken runtime
+options.  If a non-runtime option is present, that also breaks up the
+runtime options.
 
 ; {{-:?}} : Shows a list of the available runtime options and exits the program.
 
diff --git a/runtime.c b/runtime.c
index aa64092b..ac7f2392 100644
--- a/runtime.c
+++ b/runtime.c
@@ -1348,153 +1348,159 @@ void CHICKEN_parse_command_line(int argc, char *argv[], C_word *heap, C_word *st
   *stack = DEFAULT_STACK_SIZE;
   *symbols = DEFAULT_SYMBOL_TABLE_SIZE;
 
-  for(i = 1; i < C_main_argc; ++i)
-    if(!strncmp(C_main_argv[ i ], C_text("-:"), 2)) {
-      for(ptr = &C_main_argv[ i ][ 2 ]; *ptr != '\0';) {
-	switch(*(ptr++)) {
-	case '?':
-	  C_dbg("Runtime options", "\n\n"
-		 " -:?              display this text\n"
-		 " -:c              always treat stdin as console\n"
-		 " -:d              enable debug output\n"
-		 " -:D              enable more debug output\n"
-		 " -:g              show GC information\n"
-		 " -:o              disable stack overflow checks\n"
-		 " -:hiSIZE         set initial heap size\n"
-		 " -:hmSIZE         set maximal heap size\n"
-                 " -:hfSIZE         set minimum unused heap size\n"
-		 " -:hgPERCENTAGE   set heap growth percentage\n"
-		 " -:hsPERCENTAGE   set heap shrink percentage\n"
-		 " -:huPERCENTAGE   set percentage of memory used at which heap will be shrunk\n"
-		 " -:hSIZE          set fixed heap size\n"
-		 " -:r              write trace output to stderr\n"
-		 " -:RSEED          initialize rand() seed with SEED (helpful for benchmark stability)\n"
-		 " -:p              collect statistical profile and write to file at exit\n"
-		 " -:PFREQUENCY     like -:p, specifying sampling frequency in us (default: 10000)\n"
-		 " -:sSIZE          set nursery (stack) size\n"
-		 " -:tSIZE          set symbol-table size\n"
-                 " -:fSIZE          set maximal number of pending finalizers\n"
-		 " -:x              deliver uncaught exceptions of other threads to primordial one\n"
-		 " -:B              sound bell on major GC\n"
-		 " -:G              force GUI mode\n"
-		 " -:aSIZE          set trace-buffer/call-chain size\n"
-		 " -:ASIZE          set fixed temporary stack size\n"
-		 " -:H              dump heap state on exit\n"
-		 " -:S              do not handle segfaults or other serious conditions\n"
-		 "\n  SIZE may have a `k' (`K'), `m' (`M') or `g' (`G') suffix, meaning size\n"
-		 "  times 1024, 1048576, and 1073741824, respectively.\n\n");
-	  C_exit_runtime(C_fix(0));
-
-	case 'h':
-	  switch(*ptr) {
-	  case 'i':
-	    *heap = arg_val(ptr + 1); 
-	    heap_size_changed = 1;
-	    goto next;
-          case 'f':
-	    C_heap_half_min_free = arg_val(ptr + 1);
-	    goto next;
-	  case 'g':
-	    C_heap_growth = arg_val(ptr + 1);
-	    goto next;
-	  case 'm':
-	    C_maximal_heap_size = arg_val(ptr + 1);
-	    goto next;
-	  case 's':
-	    C_heap_shrinkage = arg_val(ptr + 1);
-	    goto next;
-	  case 'u':
-	    C_heap_shrinkage_used = arg_val(ptr + 1);
-	    goto next;
-	  default:
-	    *heap = arg_val(ptr); 
-	    heap_size_changed = 1;
-	    C_heap_size_is_fixed = 1;
-	    goto next;
-	  }
+  for(i = 1; i < C_main_argc; ++i) {
+    if (strncmp(C_main_argv[ i ], C_text("-:"), 2))
+      break; /* Stop parsing on first non-runtime option */
 
-	case 'o':
-	  C_disable_overflow_check = 1;
-	  break;
+    ptr = &C_main_argv[ i ][ 2 ];
+    if (*ptr == '\0')
+      break; /* Also stop parsing on first "empty" option (i.e. "-:") */
 
-	case 'B':
-	  gc_bell = 1;
-	  break;
+    do {
+      switch(*(ptr++)) {
+      case '?':
+        C_dbg("Runtime options", "\n\n"
+              " -:?              display this text\n"
+              " -:c              always treat stdin as console\n"
+              " -:d              enable debug output\n"
+              " -:D              enable more debug output\n"
+              " -:g              show GC information\n"
+              " -:o              disable stack overflow checks\n"
+              " -:hiSIZE         set initial heap size\n"
+              " -:hmSIZE         set maximal heap size\n"
+              " -:hfSIZE         set minimum unused heap size\n"
+              " -:hgPERCENTAGE   set heap growth percentage\n"
+              " -:hsPERCENTAGE   set heap shrink percentage\n"
+              " -:huPERCENTAGE   set percentage of memory used at which heap will be shrunk\n"
+              " -:hSIZE          set fixed heap size\n"
+              " -:r              write trace output to stderr\n"
+              " -:RSEED          initialize rand() seed with SEED (helpful for benchmark stability)\n"
+              " -:p              collect statistical profile and write to file at exit\n"
+              " -:PFREQUENCY     like -:p, specifying sampling frequency in us (default: 10000)\n"
+              " -:sSIZE          set nursery (stack) size\n"
+              " -:tSIZE          set symbol-table size\n"
+              " -:fSIZE          set maximal number of pending finalizers\n"
+              " -:x              deliver uncaught exceptions of other threads to primordial one\n"
+              " -:B              sound bell on major GC\n"
+              " -:G              force GUI mode\n"
+              " -:aSIZE          set trace-buffer/call-chain size\n"
+              " -:ASIZE          set fixed temporary stack size\n"
+              " -:H              dump heap state on exit\n"
+              " -:S              do not handle segfaults or other serious conditions\n"
+              "\n  SIZE may have a `k' (`K'), `m' (`M') or `g' (`G') suffix, meaning size\n"
+              "  times 1024, 1048576, and 1073741824, respectively.\n\n");
+        C_exit_runtime(C_fix(0));
+
+      case 'h':
+        switch(*ptr) {
+        case 'i':
+          *heap = arg_val(ptr + 1); 
+          heap_size_changed = 1;
+          goto next;
+        case 'f':
+          C_heap_half_min_free = arg_val(ptr + 1);
+          goto next;
+        case 'g':
+          C_heap_growth = arg_val(ptr + 1);
+          goto next;
+        case 'm':
+          C_maximal_heap_size = arg_val(ptr + 1);
+          goto next;
+        case 's':
+          C_heap_shrinkage = arg_val(ptr + 1);
+          goto next;
+        case 'u':
+          C_heap_shrinkage_used = arg_val(ptr + 1);
+          goto next;
+        default:
+          *heap = arg_val(ptr); 
+          heap_size_changed = 1;
+          C_heap_size_is_fixed = 1;
+          goto next;
+        }
 
-	case 'G':
-	  C_gui_mode = 1;
-	  break;
+      case 'o':
+        C_disable_overflow_check = 1;
+        break;
 
-	case 'H':
-	  dump_heap_on_exit = 1;
-	  break;
+      case 'B':
+        gc_bell = 1;
+        break;
 
-	case 'S':
-	  pass_serious_signals = 1;
-	  break;
+      case 'G':
+        C_gui_mode = 1;
+        break;
 
-	case 's':
-	  *stack = arg_val(ptr);
-	  stack_size_changed = 1;
-	  goto next;
+      case 'H':
+        dump_heap_on_exit = 1;
+        break;
 
-	case 'f':
-	  C_max_pending_finalizers = arg_val(ptr);
-	  goto next;
+      case 'S':
+        pass_serious_signals = 1;
+        break;
 
-	case 'a':
-	  C_trace_buffer_size = arg_val(ptr);
-	  goto next;
+      case 's':
+        *stack = arg_val(ptr);
+        stack_size_changed = 1;
+        goto next;
 
-	case 'A':
-	  fixed_temporary_stack_size = arg_val(ptr);
-	  goto next;
+      case 'f':
+        C_max_pending_finalizers = arg_val(ptr);
+        goto next;
 
-	case 't':
-	  *symbols = arg_val(ptr);
-	  goto next;
+      case 'a':
+        C_trace_buffer_size = arg_val(ptr);
+        goto next;
 
-	case 'c':
-	  fake_tty_flag = 1;
-	  break;
+      case 'A':
+        fixed_temporary_stack_size = arg_val(ptr);
+        goto next;
 
-	case 'd':
-	  debug_mode = 1;
-	  break;
+      case 't':
+        *symbols = arg_val(ptr);
+        goto next;
 
-	case 'D':
-	  debug_mode = 2;
-	  break;
+      case 'c':
+        fake_tty_flag = 1;
+        break;
 
-	case 'g':
-	  gc_report_flag = 2;
-	  break;
+      case 'd':
+        debug_mode = 1;
+        break;
 
-	case 'P':
-	  profiling = 1;
-	  profile_frequency = arg_val(ptr);
-          goto next;
+      case 'D':
+        debug_mode = 2;
+        break;
 
-	case 'p':
-	  profiling = 1;
-          break;
+      case 'g':
+        gc_report_flag = 2;
+        break;
 
-	case 'r':
-	  show_trace = 1;
-	  break;
+      case 'P':
+        profiling = 1;
+        profile_frequency = arg_val(ptr);
+        goto next;
 
-	case 'R':
-	  srand((unsigned int)arg_val(ptr));
-	  random_state_initialized = 1;
-	  goto next;
+      case 'p':
+        profiling = 1;
+        break;
 
-	case 'x':
-	  C_abort_on_thread_exceptions = 1;
-	  break;
+      case 'r':
+        show_trace = 1;
+        break;
 
-	default: panic(C_text("illegal runtime option"));
-	}
+      case 'R':
+        srand((unsigned int)arg_val(ptr));
+        random_state_initialized = 1;
+        goto next;
+
+      case 'x':
+        C_abort_on_thread_exceptions = 1;
+        break;
+
+      default: panic(C_text("illegal runtime option"));
       }
+    } while(*ptr != '\0');
 
     next:;
     }
Trap