Line data Source code
1 : #include "common.h"
2 :
3 : /*
4 : * Row layout for row-wise binding: one value and one length/indicator
5 : * per column, matching the buffer layout pyodbc uses in ExecuteMulti().
6 : */
7 : typedef struct
8 : {
9 : SQLINTEGER id1;
10 : SQLLEN ind1;
11 : char ch1[8];
12 : SQLLEN ind2;
13 : } Row;
14 :
15 : /*
16 : * Minimal reproducible example for a segfault in FreeTDS when
17 : * SQL_ATTR_PARAM_BIND_OFFSET_PTR was set on a statement handle.
18 : *
19 : * Test from Bob Kline
20 : */
21 : static void
22 10 : test_insert(void)
23 : {
24 : const char *sql;
25 : char *base;
26 :
27 : /* Three rows of actual parameter data. */
28 : static const Row rows[3] = {
29 : {10, 0, "hixxx", 2},
30 : {20, 0, "hello", 5},
31 : {30, 0, "bye", SQL_NULL_DATA},
32 : };
33 :
34 : /*
35 : * bop: the bind offset.
36 : * base(0x10) + bop == &rows[0], so a conformant driver will read
37 : * id1=10, ch1='hi' for the first row, id1=20, ch1='hello' for the second
38 : * and id1=30, ch1=NULL for the third.
39 : */
40 10 : SQLULEN bop = (SQLULEN) (TDS_INTPTR) rows - 16;
41 :
42 10 : CHKPrepare(T("INSERT INTO #offset_ptr(id1, ch1) VALUES (?, ?)"), SQL_NTS, "S");
43 :
44 : /*
45 : * Bind parameters using a non-null but deliberately invalid base
46 : * pointer (decimal 16 = 0x10). The real data buffer will be supplied
47 : * via SQL_ATTR_PARAM_BIND_OFFSET_PTR below. The ODBC spec explicitly
48 : * permits this: "either or both the offset and the address to which
49 : * the offset is added can be invalid, as long as their sum is a valid
50 : * address."
51 : */
52 10 : base = (char *) (TDS_INTPTR) 16;
53 10 : CHKBindParameter(1, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 10, 0, (SQLPOINTER) base, sizeof(SQLINTEGER),
54 : (SQLLEN *) (base + TDS_OFFSET(Row, ind1)), "S");
55 10 : CHKBindParameter(2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 10, 0, (SQLPOINTER) (base + TDS_OFFSET(Row, ch1)),
56 : sizeof(rows[0].ch1), (SQLLEN *) (base + TDS_OFFSET(Row, ind2)), "S");
57 :
58 10 : CHKSetStmtAttr(SQL_ATTR_PARAM_BIND_TYPE, (SQLPOINTER) sizeof(Row), SQL_IS_UINTEGER, "S");
59 10 : CHKSetStmtAttr(SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER) 3, SQL_IS_UINTEGER, "S");
60 10 : CHKSetStmtAttr(SQL_ATTR_PARAM_BIND_OFFSET_PTR, (SQLPOINTER) & bop, SQL_IS_POINTER, "S");
61 :
62 : /* FreeTDS crashed here with SIGSEGV in tds_convert() */
63 10 : printf("Calling SQLExecute...\n");
64 10 : CHKExecute("SI");
65 :
66 10 : odbc_reset_statement();
67 :
68 10 : sql = "IF NOT EXISTS(SELECT * FROM #offset_ptr WHERE id1 = 10 AND ch1 = 'hi') "
69 : "OR NOT EXISTS(SELECT * FROM #offset_ptr WHERE id1 = 20 AND ch1 = 'hello') "
70 : "OR NOT EXISTS(SELECT * FROM #offset_ptr WHERE id1 = 30 AND ch1 IS NULL) SELECT 1";
71 10 : odbc_check_no_row(sql);
72 :
73 10 : printf("PASS: three rows inserted.\n");
74 10 : }
75 :
76 : static void
77 90 : check_row(const Row *row, int idx, int id, const char *s)
78 : {
79 : bool s_ok;
80 :
81 90 : if (s)
82 60 : s_ok = (strcmp(row->ch1, s) == 0) && (row->ind2 == strlen(s));
83 : else
84 30 : s_ok = (row->ind2 == SQL_NULL_DATA);
85 :
86 90 : if (row->id1 != id || !s_ok) {
87 : char ch1[sizeof(row->ch1) + 1];
88 :
89 0 : memcpy(ch1, row->ch1, sizeof(row->ch1));
90 0 : ch1[sizeof(row->ch1)] = 0;
91 0 : fprintf(stderr, "Wrong row %d: id %d ch1 %s ind2 %d\n", idx, (int) row->id1, ch1, (int) row->ind2);
92 0 : exit(1);
93 : }
94 90 : }
95 :
96 : typedef enum
97 : {
98 : FETCH_NORMAL,
99 : FETCH_SCROLL,
100 : FETCH_EXTENDED,
101 : } FetchType;
102 :
103 : static void
104 30 : test_fetch(FetchType fetch_type)
105 : {
106 : char *base;
107 : Row rows[3];
108 30 : SQLULEN bop = (SQLULEN) (TDS_INTPTR) rows - 16;
109 :
110 : #ifdef HAVE_SQLROWSETSIZE
111 : SQLROWSETSIZE set_size = 0;
112 : #else
113 30 : SQLULEN set_size = 0;
114 : #endif
115 :
116 30 : memset(rows, '*', sizeof(rows));
117 :
118 30 : CHKPrepare(T("SELECT * FROM #offset_ptr ORDER BY id1"), SQL_NTS, "S");
119 :
120 30 : base = (char *) (TDS_INTPTR) 16;
121 30 : CHKBindCol(1, SQL_C_SLONG, (SQLPOINTER) base, sizeof(SQLINTEGER), (SQLLEN *) (base + TDS_OFFSET(Row, ind1)), "S");
122 30 : CHKBindCol(2, SQL_C_CHAR, (SQLPOINTER) (base + TDS_OFFSET(Row, ch1)), sizeof(rows[0].ch1),
123 : (SQLLEN *) (base + TDS_OFFSET(Row, ind2)), "S");
124 :
125 30 : CHKSetStmtAttr(SQL_ATTR_ROW_BIND_TYPE, (SQLPOINTER) sizeof(Row), SQL_IS_UINTEGER, "S");
126 30 : if (fetch_type != FETCH_EXTENDED)
127 20 : CHKSetStmtAttr(SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER) 3, SQL_IS_UINTEGER, "S");
128 : else
129 10 : CHKSetStmtAttr(SQL_ROWSET_SIZE, (SQLPOINTER) TDS_INT2PTR(3), 0, "S");
130 30 : CHKSetStmtAttr(SQL_ATTR_ROW_BIND_OFFSET_PTR, (SQLPOINTER) & bop, SQL_IS_POINTER, "S");
131 :
132 : /* FreeTDS crashed here with SIGSEGV in tds_convert() */
133 30 : printf("Calling SQLExecute...\n");
134 30 : CHKExecute("SI");
135 30 : switch (fetch_type) {
136 10 : case FETCH_NORMAL:
137 10 : CHKFetch("S");
138 10 : break;
139 10 : case FETCH_SCROLL:
140 10 : CHKFetchScroll(SQL_FETCH_NEXT, 0, "S");
141 10 : break;
142 10 : case FETCH_EXTENDED:
143 10 : CHKExtendedFetch(SQL_FETCH_NEXT, 0, &set_size, NULL, "S");
144 10 : break;
145 : }
146 60 : while (CHKMoreResults("SNo") == SQL_SUCCESS)
147 30 : continue;
148 :
149 30 : odbc_reset_statement();
150 :
151 30 : check_row(&rows[0], 0, 10, "hi");
152 30 : check_row(&rows[1], 1, 20, "hello");
153 30 : check_row(&rows[2], 2, 30, NULL);
154 :
155 30 : printf("PASS: three rows returned.\n");
156 30 : }
157 :
158 10 : TEST_MAIN()
159 : {
160 10 : odbc_use_version3 = true;
161 10 : odbc_connect();
162 :
163 10 : odbc_command("CREATE TABLE #offset_ptr (id1 INT, ch1 VARCHAR(10) NULL)");
164 :
165 10 : test_insert();
166 :
167 10 : test_fetch(FETCH_NORMAL);
168 10 : test_fetch(FETCH_SCROLL);
169 10 : test_fetch(FETCH_EXTENDED);
170 :
171 10 : odbc_disconnect();
172 10 : return 0;
173 : }
|