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
------------------------------