Skip to content

Commit d8c70e5

Browse files
authored
Fix possible nre while writing to RawCopyStream after break (#5213)
Fixes #5209
1 parent edb4d5a commit d8c70e5

File tree

3 files changed

+44
-36
lines changed

3 files changed

+44
-36
lines changed

src/Npgsql/NpgsqlRawCopyStream.cs

Lines changed: 17 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -143,25 +143,17 @@ public override void Write(ReadOnlySpan<byte> buffer)
143143
return;
144144
}
145145

146-
try
147-
{
148-
// Value is too big, flush.
149-
Flush();
150-
151-
if (buffer.Length <= _writeBuf.WriteSpaceLeft)
152-
{
153-
_writeBuf.WriteBytes(buffer);
154-
return;
155-
}
146+
// Value is too big, flush.
147+
Flush();
156148

157-
// Value is too big even after a flush - bypass the buffer and write directly.
158-
_writeBuf.DirectWrite(buffer);
159-
}
160-
catch (Exception e)
149+
if (buffer.Length <= _writeBuf.WriteSpaceLeft)
161150
{
162-
_connector.Break(e);
163-
throw;
151+
_writeBuf.WriteBytes(buffer);
152+
return;
164153
}
154+
155+
// Value is too big even after a flush - bypass the buffer and write directly.
156+
_writeBuf.DirectWrite(buffer);
165157
}
166158

167159
#if NETSTANDARD2_0
@@ -188,25 +180,17 @@ async ValueTask WriteAsyncInternal(ReadOnlyMemory<byte> buffer, CancellationToke
188180
return;
189181
}
190182

191-
try
192-
{
193-
// Value is too big, flush.
194-
await FlushAsync(true, cancellationToken);
195-
196-
if (buffer.Length <= _writeBuf.WriteSpaceLeft)
197-
{
198-
_writeBuf.WriteBytes(buffer.Span);
199-
return;
200-
}
183+
// Value is too big, flush.
184+
await FlushAsync(true, cancellationToken);
201185

202-
// Value is too big even after a flush - bypass the buffer and write directly.
203-
await _writeBuf.DirectWrite(buffer, true, cancellationToken);
204-
}
205-
catch (Exception e)
186+
if (buffer.Length <= _writeBuf.WriteSpaceLeft)
206187
{
207-
_connector.Break(e);
208-
throw;
188+
_writeBuf.WriteBytes(buffer.Span);
189+
return;
209190
}
191+
192+
// Value is too big even after a flush - bypass the buffer and write directly.
193+
await _writeBuf.DirectWrite(buffer, true, cancellationToken);
210194
}
211195
}
212196

@@ -580,4 +564,4 @@ public ValueTask DisposeAsync()
580564
Dispose();
581565
return default;
582566
}
583-
}
567+
}

test/Npgsql.Tests/CopyTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Threading;
99
using System.Threading.Tasks;
1010
using Npgsql.Internal;
11+
using Npgsql.Tests.Support;
1112
using NpgsqlTypes;
1213
using NUnit.Framework;
1314
using static Npgsql.Tests.TestUtil;
@@ -1127,6 +1128,29 @@ public async Task Copy_to_is_not_supported_in_regular_command_execution()
11271128
Assert.That(() => conn.ExecuteNonQuery($@"COPY {table} (foo) TO stdin"), Throws.Exception.TypeOf<NotSupportedException>());
11281129
}
11291130

1131+
[Test, IssueLink("https://github.com/npgsql/npgsql/issues/5209")]
1132+
[Platform(Exclude = "MacOsX", Reason = "Write might not throw an exception")]
1133+
public async Task RawBinaryCopy_write_nre([Values] bool async)
1134+
{
1135+
await using var postmasterMock = PgPostmasterMock.Start(ConnectionString);
1136+
await using var dataSource = CreateDataSource(postmasterMock.ConnectionString);
1137+
await using var conn = await dataSource.OpenConnectionAsync();
1138+
1139+
var server = await postmasterMock.WaitForServerConnection();
1140+
await server
1141+
.WriteCopyInResponse(isBinary: true)
1142+
.FlushAsync();
1143+
1144+
await using var stream = await conn.BeginRawBinaryCopyAsync("COPY SomeTable (field_text, field_int4) FROM STDIN");
1145+
server.Close();
1146+
var value = Encoding.UTF8.GetBytes(new string('a', conn.Settings.WriteBufferSize * 2));
1147+
if (async)
1148+
Assert.ThrowsAsync<NpgsqlException>(async () => await stream.WriteAsync(value));
1149+
else
1150+
Assert.Throws<NpgsqlException>(() => stream.Write(value));
1151+
Assert.That(conn.FullState, Is.EqualTo(ConnectionState.Broken));
1152+
}
1153+
11301154
#endregion
11311155

11321156
#region Utils

test/Npgsql.Tests/Support/PgServerMock.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -328,12 +328,12 @@ internal PgServerMock WriteBackendKeyData(int processId, int secret)
328328
internal PgServerMock WriteCancellationResponse()
329329
=> WriteErrorResponse(PostgresErrorCodes.QueryCanceled, "Cancellation", "Query cancelled");
330330

331-
internal PgServerMock WriteCopyInResponse()
331+
internal PgServerMock WriteCopyInResponse(bool isBinary = false)
332332
{
333333
CheckDisposed();
334334
_writeBuffer.WriteByte((byte)BackendMessageCode.CopyInResponse);
335335
_writeBuffer.WriteInt32(5);
336-
_writeBuffer.WriteByte(0);
336+
_writeBuffer.WriteByte(isBinary ? (byte)1 : (byte)0);
337337
_writeBuffer.WriteInt16(1);
338338
_writeBuffer.WriteInt16(0);
339339
return this;
@@ -381,4 +381,4 @@ public void Dispose()
381381

382382
_disposed = true;
383383
}
384-
}
384+
}

0 commit comments

Comments
 (0)