Skip to content

Commit 12a61c1

Browse files
WXbetatvcaptain
authored andcommitted
[SoftCSA] Add double-buffered keys to eliminate data race in descrambling (#3702)
* [SoftCSA] Add double-buffered keys to eliminate data race in descrambling setKey() (CWHandler thread) and descramble() (recorder thread) access the same dvbcsa_bs_key_t concurrently. key_set_ecm() rewrites the internal key schedule while decrypt() reads it, causing intermittent audio dropouts and brief freezes. Each parity now has two key slots. setKey() writes the inactive slot, then atomically publishes the new index (release). descramble() snapshots the active index once per call (acquire) and uses it throughout, so it never sees a partially-written key schedule. * [cwhandler] Fix build warnings treated as errors - Check return value of ::write() calls to wake pipe - Replace EWOULDBLOCK with EINTR (EAGAIN == EWOULDBLOCK on Linux, causing -Werror=logical-op failure)
1 parent d415daf commit 12a61c1

File tree

3 files changed

+71
-47
lines changed

3 files changed

+71
-47
lines changed

lib/dvb/csaengine.cpp

Lines changed: 50 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,8 @@ bool csa_load_library()
8686
}
8787

8888
eDVBCSAEngine::eDVBCSAEngine()
89-
: m_key_even(nullptr)
90-
, m_key_odd(nullptr)
89+
: m_key_even{nullptr, nullptr}
90+
, m_key_odd{nullptr, nullptr}
9191
, m_batch_size(0)
9292
{
9393
}
@@ -96,15 +96,18 @@ eDVBCSAEngine::~eDVBCSAEngine()
9696
{
9797
if (g_csa_api.available && g_csa_api.key_free)
9898
{
99-
if (m_key_even)
99+
for (int i = 0; i < 2; ++i)
100100
{
101-
g_csa_api.key_free(m_key_even);
102-
m_key_even = nullptr;
103-
}
104-
if (m_key_odd)
105-
{
106-
g_csa_api.key_free(m_key_odd);
107-
m_key_odd = nullptr;
101+
if (m_key_even[i])
102+
{
103+
g_csa_api.key_free(m_key_even[i]);
104+
m_key_even[i] = nullptr;
105+
}
106+
if (m_key_odd[i])
107+
{
108+
g_csa_api.key_free(m_key_odd[i]);
109+
m_key_odd[i] = nullptr;
110+
}
108111
}
109112
}
110113
}
@@ -118,13 +121,16 @@ bool eDVBCSAEngine::init()
118121
}
119122

120123
m_batch_size = g_csa_api.batch_size();
121-
m_key_even = g_csa_api.key_alloc();
122-
m_key_odd = g_csa_api.key_alloc();
123124

124-
if (!m_key_even || !m_key_odd)
125+
for (int i = 0; i < 2; ++i)
125126
{
126-
eWarning("[eDVBCSAEngine] init: key_alloc failed");
127-
return false;
127+
m_key_even[i] = g_csa_api.key_alloc();
128+
m_key_odd[i] = g_csa_api.key_alloc();
129+
if (!m_key_even[i] || !m_key_odd[i])
130+
{
131+
eWarning("[eDVBCSAEngine] init: key_alloc failed");
132+
return false;
133+
}
128134
}
129135

130136
// Pre-allocate batch arrays
@@ -201,7 +207,7 @@ void eDVBCSAEngine::setKey(int parity, uint8_t ecm_mode, const uint8_t* cw)
201207
{
202208
if (!cw)
203209
return;
204-
if (!m_key_even || !m_key_odd)
210+
if (!m_key_even[0] || !m_key_even[1] || !m_key_odd[0] || !m_key_odd[1])
205211
return;
206212
if (!g_csa_api.available || !g_csa_api.key_set_ecm)
207213
return;
@@ -211,16 +217,24 @@ void eDVBCSAEngine::setKey(int parity, uint8_t ecm_mode, const uint8_t* cw)
211217
BYTE_HEX(cw[0]), BYTE_HEX(cw[1]), BYTE_HEX(cw[2]), BYTE_HEX(cw[3]),
212218
BYTE_HEX(cw[4]), BYTE_HEX(cw[5]), BYTE_HEX(cw[6]), BYTE_HEX(cw[7]));
213219

214-
// Lock-free key update
220+
// Double-buffered key update: write to inactive slot, then swap index.
221+
// descramble() on the recorder thread reads the active slot via atomic index,
222+
// so it never sees a partially-written key schedule.
215223
if (parity == 0) // even
216224
{
217-
g_csa_api.key_set_ecm(ecm_mode, cw, m_key_even);
218-
m_key_even_set = true;
225+
int active = m_key_even_idx.load(std::memory_order_relaxed);
226+
int inactive = 1 - active;
227+
g_csa_api.key_set_ecm(ecm_mode, cw, m_key_even[inactive]);
228+
m_key_even_idx.store(inactive, std::memory_order_release);
229+
m_key_even_set.store(true, std::memory_order_release);
219230
}
220231
else // odd
221232
{
222-
g_csa_api.key_set_ecm(ecm_mode, cw, m_key_odd);
223-
m_key_odd_set = true;
233+
int active = m_key_odd_idx.load(std::memory_order_relaxed);
234+
int inactive = 1 - active;
235+
g_csa_api.key_set_ecm(ecm_mode, cw, m_key_odd[inactive]);
236+
m_key_odd_idx.store(inactive, std::memory_order_release);
237+
m_key_odd_set.store(true, std::memory_order_release);
224238
}
225239

226240
if (g_csa_api.get_ecm_table)
@@ -273,13 +287,21 @@ void eDVBCSAEngine::descramble(unsigned char* packets, int len)
273287
{
274288
if (!packets || len <= 0)
275289
return;
276-
if (!m_key_even || !m_key_odd || m_batch_size <= 0)
290+
if (m_batch_size <= 0)
277291
return;
278292
if (!g_csa_api.available || !g_csa_api.decrypt)
279293
return;
280294
if (m_batch_even.empty() || m_batch_odd.empty())
281295
return;
282296

297+
// Snapshot active key state and indices once for this entire buffer.
298+
// setKey() on the CWHandler thread may swap the index at any time,
299+
// but we consistently use the snapshot throughout this call.
300+
const bool even_set = m_key_even_set.load(std::memory_order_acquire);
301+
const bool odd_set = m_key_odd_set.load(std::memory_order_acquire);
302+
dvbcsa_bs_key_t* key_even = even_set ? m_key_even[m_key_even_idx.load(std::memory_order_acquire)] : nullptr;
303+
dvbcsa_bs_key_t* key_odd = odd_set ? m_key_odd[m_key_odd_idx.load(std::memory_order_acquire)] : nullptr;
304+
283305
int i = 0;
284306
int even_cnt = 0;
285307
int odd_cnt = 0;
@@ -290,9 +312,6 @@ void eDVBCSAEngine::descramble(unsigned char* packets, int len)
290312

291313
CSA_LOG("[eDVBCSAEngine] descramble: len=%d batch_size=%d", len, m_batch_size);
292314

293-
const bool even_set = m_key_even_set;
294-
const bool odd_set = m_key_odd_set;
295-
296315
while (i < len)
297316
{
298317
unsigned char *pkt = packets + i;
@@ -308,7 +327,7 @@ void eDVBCSAEngine::descramble(unsigned char* packets, int len)
308327

309328
if (scrambled == 2) // even
310329
{
311-
if (even_set)
330+
if (key_even)
312331
{
313332
// Key available: descramble and clear TSC
314333
clearTSC(pkt);
@@ -326,7 +345,7 @@ void eDVBCSAEngine::descramble(unsigned char* packets, int len)
326345
}
327346
else if (scrambled == 3) // odd
328347
{
329-
if (odd_set)
348+
if (key_odd)
330349
{
331350
// Key available: descramble and clear TSC
332351
clearTSC(pkt);
@@ -347,8 +366,7 @@ void eDVBCSAEngine::descramble(unsigned char* packets, int len)
347366
if (even_cnt == m_batch_size)
348367
{
349368
pcks_even[even_cnt].data = NULL;
350-
if (even_set)
351-
g_csa_api.decrypt(m_key_even, pcks_even, 184);
369+
g_csa_api.decrypt(key_even, pcks_even, 184);
352370
CSA_LOG("[eDVBCSAEngine] decrypt even batch (%d)", m_batch_size);
353371
even_cnt = 0;
354372
}
@@ -357,8 +375,7 @@ void eDVBCSAEngine::descramble(unsigned char* packets, int len)
357375
if (odd_cnt == m_batch_size)
358376
{
359377
pcks_odd[odd_cnt].data = NULL;
360-
if (odd_set)
361-
g_csa_api.decrypt(m_key_odd, pcks_odd, 184);
378+
g_csa_api.decrypt(key_odd, pcks_odd, 184);
362379
CSA_LOG("[eDVBCSAEngine] decrypt odd batch (%d)", m_batch_size);
363380
odd_cnt = 0;
364381
}
@@ -370,17 +387,15 @@ void eDVBCSAEngine::descramble(unsigned char* packets, int len)
370387
if (even_cnt > 0)
371388
{
372389
pcks_even[even_cnt].data = NULL;
373-
if (even_set)
374-
g_csa_api.decrypt(m_key_even, pcks_even, 184);
390+
g_csa_api.decrypt(key_even, pcks_even, 184);
375391
CSA_LOG("[eDVBCSAEngine] decrypt remaining even packets=%d", even_cnt);
376392
}
377393

378394
// flush remaining odd
379395
if (odd_cnt > 0)
380396
{
381397
pcks_odd[odd_cnt].data = NULL;
382-
if (odd_set)
383-
g_csa_api.decrypt(m_key_odd, pcks_odd, 184);
398+
g_csa_api.decrypt(key_odd, pcks_odd, 184);
384399
CSA_LOG("[eDVBCSAEngine] decrypt remaining odd packets=%d", odd_cnt);
385400
}
386401

lib/dvb/csaengine.h

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,13 @@ bool csa_load_library();
4747
/**
4848
* eDVBCSAEngine - Low-Level CSA Descrambler
4949
*
50-
* Stateless wrapper around libdvbcsa.
51-
* Performs only the actual descrambling.
52-
* Knows nothing about services, algos - only keys and data.
50+
* Thread-safe wrapper around libdvbcsa using double-buffered keys.
51+
* setKey() from CWHandler thread and descramble() from recorder thread
52+
* can run concurrently without locks.
53+
*
54+
* Each parity (even/odd) has two key slots. setKey() writes into the
55+
* inactive slot, then atomically swaps the active index. descramble()
56+
* snapshots the active index once per call and uses it throughout.
5357
*/
5458
class eDVBCSAEngine
5559
{
@@ -69,11 +73,11 @@ class eDVBCSAEngine
6973
static std::string getLibraryPath();
7074
static std::string getLibraryVersion();
7175

72-
// Key Management
76+
// Key Management (thread-safe, called from CWHandler thread)
7377
void setKey(int parity, uint8_t ecm_mode, const uint8_t* cw);
7478
void clearKeys();
7579

76-
// Descrambling
80+
// Descrambling (called from recorder thread)
7781
void descramble(unsigned char* packets, int len);
7882

7983
// Status
@@ -83,8 +87,13 @@ class eDVBCSAEngine
8387
int getBatchSizeInstance() const { return m_batch_size; }
8488

8589
private:
86-
dvbcsa_bs_key_t* m_key_even;
87-
dvbcsa_bs_key_t* m_key_odd;
90+
// Double-buffered keys: two slots per parity, atomic index selects active one.
91+
// setKey() writes to inactive slot, then swaps index.
92+
// descramble() reads active slot via snapshot of index.
93+
dvbcsa_bs_key_t* m_key_even[2];
94+
dvbcsa_bs_key_t* m_key_odd[2];
95+
std::atomic<int> m_key_even_idx{0}; // active index for even key (0 or 1)
96+
std::atomic<int> m_key_odd_idx{0}; // active index for odd key (0 or 1)
8897
std::atomic<bool> m_key_even_set{false};
8998
std::atomic<bool> m_key_odd_set{false};
9099
int m_batch_size;

lib/dvb/cwhandler.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ eDVBCWHandler::~eDVBCWHandler()
5151
if (m_wake_pipe[1] >= 0)
5252
{
5353
char c = 'q';
54-
::write(m_wake_pipe[1], &c, 1);
54+
ssize_t ret __attribute__((unused)) = ::write(m_wake_pipe[1], &c, 1);
5555
}
5656

5757
if (m_thread)
@@ -160,7 +160,7 @@ int eDVBCWHandler::addConnection(int softcam_fd)
160160

161161
// Wake up poll loop to pick up new connection
162162
char c = 'w';
163-
::write(m_wake_pipe[1], &c, 1);
163+
ssize_t ret __attribute__((unused)) = ::write(m_wake_pipe[1], &c, 1);
164164

165165
eDebug("[eDVBCWHandler] Added connection: softcam_fd=%d, proxy_fd=%d, client_fd=%d", softcam_fd, pair[0], pair[1]);
166166
return pair[1]; // return the fd for ePMTClient
@@ -180,7 +180,7 @@ void eDVBCWHandler::removeConnection(int client_fd)
180180

181181
// Wake up poll loop
182182
char c = 'w';
183-
::write(m_wake_pipe[1], &c, 1);
183+
ssize_t ret __attribute__((unused)) = ::write(m_wake_pipe[1], &c, 1);
184184
return;
185185
}
186186
}
@@ -271,7 +271,7 @@ void eDVBCWHandler::threadLoop()
271271
ssize_t w = ::write(conn.proxy_fd, buf + written, n - written);
272272
if (w < 0)
273273
{
274-
if (errno == EAGAIN || errno == EWOULDBLOCK)
274+
if (errno == EAGAIN || errno == EINTR)
275275
{
276276
// Socketpair buffer full (MainLoop not reading) - drop this chunk
277277
// CWs were already intercepted and setKey() called
@@ -319,7 +319,7 @@ void eDVBCWHandler::threadLoop()
319319
ssize_t w = ::write(conn.softcam_fd, buf + written, n - written);
320320
if (w < 0)
321321
{
322-
if (errno == EAGAIN || errno == EWOULDBLOCK)
322+
if (errno == EAGAIN || errno == EINTR)
323323
{
324324
// Brief spin-wait for softcam to catch up (small CAPMT packets)
325325
usleep(1000);

0 commit comments

Comments
 (0)