Skip to main content

    
Key Takeaways:
Do not use dimensioned redimensionable-array elements for EXECUTE variable targets for CAPTURING, RTNLIST and SETTING and variable sources for PASSLIST.
The areas of concern:
1. Information style redimensionable arrays may change size in the executed program.
2. Arrays of either flavor compute to a temporary in a PASSLIST that gets overwritten by constructing the command string in a concatenation. This UniVerse bug results in the command string passed to the command as the select list. 

Environment:
UniVerse 11.3.1.6022
Fedora Linux

UniVerse supports two kinds of array. 

Pick sytle arrays have fixed dimensions at compile time. Each element of the array takes one VARTAB (variable table) slot. VARTAB uses an unsigned short for an index which limits the total number of slots in a compilation unit, program or subroutine, to 64K elements. (This limits an array to a maximum of 64K elements.)

Information style arrays specify dimensions at run time and may change and consumes only one VARTAB slot. The VARTAB entry contains a DATUM descriptor containing the current X and Y dimensions and a pointer to a vector of DATUM for each array element.

A program using EXECUTE can capture the execute command output to a variable. That variable may be an element of an array.

program X
$OPTIONS INFORMATION
COMMON /Vars/ A(2,3),B(2,3)
EXECUTE "SomeCommand" CAPTURING A(2,2)
crt A(2,2); * Display captured output

UniVerse uses the matrix_load operation to create a pointer to A(2,2) for the output, specified in the exe_out opcode.

00004: execute "SomeCommand" capturing A(2,2)
00004 00084 : 0E8 matrix_load    A 2 2 
00004 0008C : 074 exe_out         => $MATRIX 
00004 00090 : 07E execute        "SomeCommand" 

Again, you can redimension Information flavor arrays. What happens if "SomeCommand" is a program that reduces the dimension of A so the pointer to A(2,2) is beyond the end of the array? 

>CT VOC RELLEVEL

     RELLEVEL
0001 X
0002 11.3.1
0003 INFORMATION
0004 INFORMATION.FORMAT
0005 11.3.1
>CT BP EXECUTE1

     EXECUTE1
0001 program EXECUTE1
0002 * EXECUTE program that redimensions our target output matrix.
0003 $options INFORMATION
0004 
0005 common /Vars/ A(2,3), B(2,3)
0006 
0007 dim A(2,3), B(2,3)
0008 
0009 Element = 0
0010 for X = 1 to 2
0011     for Y = 1 to 3
0012         Element += 1
0013         A(X,Y) = "A":Element
0014         B(X,Y) = "B":Element
0015     next Y
0016 next X
0017 
0018 execute "RUN BP EXECUTE1A" capturing A(2,2)
0019 
0020 * Display contents of A
0021 Dimensions = inmat(A)
0022 AX = Dimensions<1,1>
0023 AY = Dimensions<1,2>
0024 for X = 1 to AX
0025     for Y = 1 to AY
0026         if unassigned(A(X,Y))
0027         then print "A(":X:",":Y:"): Undefined."
0028         else print "A(":X:",":Y:"): ":A(X,Y)
0029     next Y
0030 next X
0031 
0032 * Display contents of B
0033 Dimensions = inmat(B)
0034 BX = Dimensions<1,1>
0035 BY = Dimensions<1,2>
0036 for X = 1 to BX
0037     for Y = 1 to BY
0038         if unassigned(B(X,Y))
0039         then print "B(":X:",":Y:"): Undefined."
0040         else print "B(":X:",":Y:"): ":B(X,Y)
0041     next Y
0042 next X
0043 
0044 end
0045 
>CT BP EXECUTE1A

     EXECUTE1A
0001 subroutine EXECUTE1A
0002 * Redimension COMMON Matrix
0003 $options INFORMATION
0004 
0005 common /Vars/ A(2,3), B(2,3)
0006 
0007 dim A(1,2)
0008 print str("a",5) : str("b",5) : str("c",5); * For CAPTURING
0009 
0010 return
0011 end
0012 
>BASIC BP EXECUTE1 EXECUTE1A
Compiling: Source = 'BP/EXECUTE1', Object = 'BP.O/EXECUTE1'
****

Compilation Complete.
Compiling: Source = 'BP/EXECUTE1A', Object = 'BP.O/EXECUTE1A'
*

Compilation Complete.
>RUN BP EXECUTE1
A(1,1): A1
A(1,2): A2
B(1,1): B1
B(1,2): B2
B(1,3): B3
B(2,1): B4
B(2,2): B5
B(2,3): B6
>

UniVerse uses C language-supplied malloc libraries for memory management. This memory holds information in DATUM structures for integers, numbers, string descriptors, the strings, and other things.

The $MATRIX temporary variable still has a pointer to *somewhere*. There probably is not enough information to know that $MATRIX points beyond the end of the current array dimensions. If we are lucky, UniVere will create a string datum in malloc space. With no variable for the output, creating a memory leak. In the worst case we have overwritten memory in a way that could cause a crash.

So, we can test that a bit more. Copy EXECUTE1 to EXECUTE2, changing the run program to EXECUTE2A. Copy EXECUTE1A to EXECUTE2A so that it changes the dimension from A(2,3) to A(3,2).

>CT BP EXECUTE2A

     EXECUTE2A
0001 subroutine EXECUTE2A
0002 * Redimension COMMON Matrix
0003 $options INFORMATION
0004 
0005 common /Vars/ A(2,3), B(2,3)
0006 
0007 dim A(3,2)
0008 print str("a",5) : str("b",5) : str("c",5); * For CAPTURING
0009 
0010 return
0011 end
0012 
>RUN BP EXECUTE2
A(1,1): A1
A(1,2): A2
A(2,1): A4
A(2,2): A5
A(3,1): Undefined.
A(3,2): Undefined.
B(1,1): B1
B(1,2): B2
B(1,3): B3
B(2,1): B4
B(2,2): B5
B(2,3): B6

To redimension A, UniVerse needed to build a new vector of DATUM elements. The original A(2,2) points into the old vector. Again, the output when to unknown memory.

Let's alter the executed program to cause memory blocks to move around. Again, copy EXECUTE1 to EXECUTE3 and make it run EXECUTE3A. 

This time, EXECUTE3A performs more activity.

>CT BP EXECUTE3A

     EXECUTE3A
0001 subroutine EXECUTE3A
0002 * Redimension COMMON Matrix
0003 $options DEFAULT
0004 
0005 common /Vars/ A(2,3), B(2,3)
0006 
0007 dim A(1,2)
0008 
0009 print str("a",5) : str("b",5) : str("c",5); * For CAPTURING
0010 
0011 dim B(20,30)
0012 dim A(20,30)
0013 
0014 dim A1(2,3)
0015 Element = 0
0016 for X = 1 to 2
0017     for Y = 1 to 3
0018         Element += 1
0019         A1(X,Y) = "A1":Element
0020     next Y
0021 next X
0022 
0023 dim B(2,3)
0024 dim A(2,3)
0025 return
0026 end
0027 

Voilà. With EXECUTE3A changing memory layout, we have caused A(2,2) to end up in B(2,2)!

>RUN BP EXECUTE3
A(1,1): A1
A(1,2): A2
A(1,3): Undefined.
A(2,1): Undefined.
A(2,2): Undefined.
A(2,3): Undefined.
B(1,1): B1
B(1,2): B2
B(1,3): B3
B(2,1): B4
B(2,2): aaaaabbbbbccccc�
B(2,3): B6

We assume:
1. The output string exists in a properly allocated malloc block.
2. A string DATUM, consisting of a length and a pointer to the output string.
3. The string DATUM overwrote whatever was in the location of the original A(2,2). That could be in the middle of another string, another DATUM, or free space. 

One mitigation strategy would compute the array element values to temporary variables, and then store into the array upon return. At this point, storing the value into the array coud throw an out of bounds error.


==============================================================================

The BASIC compiler for EXECUTE fails to track temporary variables properly and reuses one before it is done with it. This experiment fails with both fixed and redimensionable arrays. 

>CT BP EXECUTE4

     EXECUTE4
0001 program EXECUTE4
0002 
0003 dim Pass(4)
0004 X = 2
0005 Pass(X) = "TO" : @fm : "FROM"
0006 Cmnd1 = "SELECT VOC "
0007 Cmnd2 = ""
0008 execute Cmnd1:Cmnd2 passlist Pass(X)
0009 
0010 end


Here, the command for the EXECUTE requires calculation, so UniVerse creates a temporary variable for concatenating the two parts of the command. Unfortunately, it does not first increment the temporary variable number and use $R1. This causes the command in $R0, "SELECT VOC " to be passed to the SELECT statement as the SELECT list.

>BASIC BP EXECUTE4
Compiling: Source = 'BP/EXECUTE4', Object = 'BP.O/EXECUTE4'
*

Compilation Complete.
>VLIST BP EXECUTE4
. . .
00008: execute Cmnd1:Cmnd2 passlist Pass(X)
00008 00024 : 0E6 matrix         Pass X 1  => $R0 
00008 0002E : 078 exe_sin        0 $R0 
00008 00034 : 03A concat         Cmnd1 Cmnd2  => $R0 
00008 0003C : 07E execute        $R0 

>RUN BP EXECUTE4

0 record(s) selected to SELECT list #0.
"SELECT VOC " not found.
>



------------------------------
Mark A Baldridge
Principal Consultant
Thought Mirror
Nacogdoches, Texas United States
------------------------------