Skip to content

Commit 419e3ad

Browse files
authored
Fixed memory exhaustion during encryption key re-encryption (#571)
1 parent 2e4d959 commit 419e3ad

File tree

6 files changed

+106
-130
lines changed

6 files changed

+106
-130
lines changed

app/code/core/Mage/Checkout/Model/Observer.php

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -46,25 +46,15 @@ public function encryptionKeyRegenerated(\Maho\Event\Observer $observer): void
4646
$output = $observer->getEvent()->getOutput();
4747
$encryptCallback = $observer->getEvent()->getEncryptCallback();
4848
$decryptCallback = $observer->getEvent()->getDecryptCallback();
49-
$readConnection = Mage::getSingleton('core/resource')->getConnection('core_read');
50-
$writeConnection = Mage::getSingleton('core/resource')->getConnection('core_write');
5149

5250
$output->write('Re-encrypting data on sales_flat_quote table... ');
53-
54-
$table = Mage::getSingleton('core/resource')->getTableName('sales_flat_quote');
55-
$select = $readConnection->select()
56-
->from($table)
57-
->where('password_hash IS NOT NULL');
58-
$encryptedData = $readConnection->fetchAll($select);
59-
60-
foreach ($encryptedData as $encryptedDataRow) {
61-
$writeConnection->update(
62-
$table,
63-
['password_hash' => $encryptCallback($decryptCallback($encryptedDataRow['password_hash']))],
64-
['entity_id = ?' => $encryptedDataRow['entity_id']],
65-
);
66-
}
67-
51+
Mage::helper('core')->recryptTable(
52+
Mage::getSingleton('core/resource')->getTableName('sales_flat_quote'),
53+
'entity_id',
54+
['password_hash'],
55+
$encryptCallback,
56+
$decryptCallback,
57+
);
6858
$output->writeln('OK');
6959
}
7060
}

app/code/core/Mage/Core/Helper/Data.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,4 +1163,66 @@ public function getIconSvg(string $name, string $variant = 'outline', string $ro
11631163
$iconSvg = str_replace('<svg ', '<svg role="' . $role . '" ', $iconSvg);
11641164
return $iconSvg;
11651165
}
1166+
1167+
/**
1168+
* Re-encrypt columns in a table using batched queries to avoid memory exhaustion.
1169+
*
1170+
* @param string[] $columns
1171+
*/
1172+
public function recryptTable(
1173+
string $table,
1174+
string $primaryKey,
1175+
array $columns,
1176+
callable $encryptCallback,
1177+
callable $decryptCallback,
1178+
int $batchSize = 1000,
1179+
): void {
1180+
$readConnection = Mage::getSingleton('core/resource')->getConnection('core_read');
1181+
$writeConnection = Mage::getSingleton('core/resource')->getConnection('core_write');
1182+
$lastId = 0;
1183+
1184+
$quotedPk = $readConnection->quoteIdentifier($primaryKey);
1185+
1186+
while (true) {
1187+
$select = $readConnection->select()
1188+
->from($table, array_merge([$primaryKey], $columns))
1189+
->where("$quotedPk > ?", $lastId)
1190+
->order("$quotedPk ASC")
1191+
->limit($batchSize);
1192+
1193+
$conditions = [];
1194+
foreach ($columns as $column) {
1195+
$conditions[] = $readConnection->quoteIdentifier($column) . ' IS NOT NULL AND '
1196+
. $readConnection->quoteIdentifier($column) . " != ''";
1197+
}
1198+
$select->where(implode(' OR ', $conditions));
1199+
1200+
$rows = $readConnection->fetchAll($select);
1201+
if (empty($rows)) {
1202+
break;
1203+
}
1204+
1205+
foreach ($rows as $row) {
1206+
$updateData = [];
1207+
foreach ($columns as $column) {
1208+
if ($row[$column] !== null && $row[$column] !== '') {
1209+
$decrypted = $decryptCallback($row[$column]);
1210+
if ($decrypted !== '') {
1211+
$updateData[$column] = $encryptCallback($decrypted);
1212+
}
1213+
}
1214+
}
1215+
if (!empty($updateData)) {
1216+
$writeConnection->update(
1217+
$table,
1218+
$updateData,
1219+
["$quotedPk = ?" => $row[$primaryKey]],
1220+
);
1221+
}
1222+
$lastId = $row[$primaryKey];
1223+
}
1224+
1225+
unset($rows);
1226+
}
1227+
}
11661228
}

app/code/core/Mage/Payment/Model/Observer.php

Lines changed: 14 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -165,53 +165,25 @@ public function encryptionKeyRegenerated(\Maho\Event\Observer $observer): void
165165
$output = $observer->getEvent()->getOutput();
166166
$encryptCallback = $observer->getEvent()->getEncryptCallback();
167167
$decryptCallback = $observer->getEvent()->getDecryptCallback();
168-
$readConnection = Mage::getSingleton('core/resource')->getConnection('core_read');
169-
$writeConnection = Mage::getSingleton('core/resource')->getConnection('core_write');
170168

171169
$output->write('Re-encrypting data on sales_flat_quote_payment table... ');
172-
$table = Mage::getSingleton('core/resource')->getTableName('sales_flat_quote_payment');
173-
174-
$select = $readConnection->select()
175-
->from($table)
176-
->where('cc_number_enc IS NOT NULL');
177-
$encryptedData = $readConnection->fetchAll($select);
178-
foreach ($encryptedData as $encryptedDataRow) {
179-
$writeConnection->update(
180-
$table,
181-
['cc_number_enc' => $encryptCallback($decryptCallback($encryptedDataRow['cc_number_enc']))],
182-
['payment_id = ?' => $encryptedDataRow['payment_id']],
183-
);
184-
}
185-
186-
$select = $readConnection->select()
187-
->from($table)
188-
->where('cc_cid_enc IS NOT NULL');
189-
$encryptedData = $readConnection->fetchAll($select);
190-
foreach ($encryptedData as $encryptedDataRow) {
191-
$writeConnection->update(
192-
$table,
193-
['cc_cid_enc' => $encryptCallback($decryptCallback($encryptedDataRow['cc_cid_enc']))],
194-
['payment_id = ?' => $encryptedDataRow['payment_id']],
195-
);
196-
}
197-
170+
Mage::helper('core')->recryptTable(
171+
Mage::getSingleton('core/resource')->getTableName('sales_flat_quote_payment'),
172+
'payment_id',
173+
['cc_number_enc', 'cc_cid_enc'],
174+
$encryptCallback,
175+
$decryptCallback,
176+
);
198177
$output->writeln('OK');
199178

200179
$output->write('Re-encrypting data on sales_flat_order_payment table... ');
201-
$table = Mage::getSingleton('core/resource')->getTableName('sales_flat_order_payment');
202-
203-
$select = $readConnection->select()
204-
->from($table)
205-
->where('cc_number_enc IS NOT NULL');
206-
$encryptedData = $readConnection->fetchAll($select);
207-
foreach ($encryptedData as $encryptedDataRow) {
208-
$writeConnection->update(
209-
$table,
210-
['cc_number_enc' => $encryptCallback($decryptCallback($encryptedDataRow['cc_number_enc']))],
211-
['entity_id = ?' => $encryptedDataRow['entity_id']],
212-
);
213-
}
214-
180+
Mage::helper('core')->recryptTable(
181+
Mage::getSingleton('core/resource')->getTableName('sales_flat_order_payment'),
182+
'entity_id',
183+
['cc_number_enc', 'cc_cid_enc'],
184+
$encryptCallback,
185+
$decryptCallback,
186+
);
215187
$output->writeln('OK');
216188
}
217189
}

app/code/core/Maho/AdminActivityLog/Model/Observer.php

Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -462,38 +462,15 @@ public function encryptionKeyRegenerated(\Maho\Event\Observer $observer): void
462462
$output = $observer->getEvent()->getOutput();
463463
$encryptCallback = $observer->getEvent()->getEncryptCallback();
464464
$decryptCallback = $observer->getEvent()->getDecryptCallback();
465-
$readConnection = Mage::getSingleton('core/resource')->getConnection('core_read');
466-
$writeConnection = Mage::getSingleton('core/resource')->getConnection('core_write');
467465

468466
$output->write('Re-encrypting data on adminactivitylog_activity table... ');
469-
$table = Mage::getSingleton('core/resource')->getTableName('adminactivitylog/activity');
470-
471-
// Re-encrypt old_data
472-
$select = $readConnection->select()
473-
->from($table)
474-
->where('old_data IS NOT NULL');
475-
$encryptedData = $readConnection->fetchAll($select);
476-
foreach ($encryptedData as $encryptedDataRow) {
477-
$writeConnection->update(
478-
$table,
479-
['old_data' => $encryptCallback($decryptCallback($encryptedDataRow['old_data']))],
480-
['activity_id = ?' => $encryptedDataRow['activity_id']],
481-
);
482-
}
483-
484-
// Re-encrypt new_data
485-
$select = $readConnection->select()
486-
->from($table)
487-
->where('new_data IS NOT NULL');
488-
$encryptedData = $readConnection->fetchAll($select);
489-
foreach ($encryptedData as $encryptedDataRow) {
490-
$writeConnection->update(
491-
$table,
492-
['new_data' => $encryptCallback($decryptCallback($encryptedDataRow['new_data']))],
493-
['activity_id = ?' => $encryptedDataRow['activity_id']],
494-
);
495-
}
496-
467+
Mage::helper('core')->recryptTable(
468+
Mage::getSingleton('core/resource')->getTableName('adminactivitylog/activity'),
469+
'activity_id',
470+
['old_data', 'new_data'],
471+
$encryptCallback,
472+
$decryptCallback,
473+
);
497474
$output->writeln('OK');
498475
}
499476

app/code/core/Maho/FeedManager/Model/Observer.php

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,31 +22,14 @@ public function encryptionKeyRegenerated(Maho\Event\Observer $observer): void
2222
$encryptCallback = $observer->getEvent()->getEncryptCallback();
2323
$decryptCallback = $observer->getEvent()->getDecryptCallback();
2424

25-
$readConnection = Mage::getSingleton('core/resource')->getConnection('core_read');
26-
$writeConnection = Mage::getSingleton('core/resource')->getConnection('core_write');
27-
2825
$output->write('Re-encrypting data on feedmanager_destination table... ');
29-
30-
$table = Mage::getSingleton('core/resource')->getTableName('feedmanager/destination');
31-
32-
$select = $readConnection->select()
33-
->from($table, ['destination_id', 'config'])
34-
->where('config IS NOT NULL')
35-
->where('config != ?', '');
36-
37-
$destinations = $readConnection->fetchAll($select);
38-
39-
foreach ($destinations as $row) {
40-
$decrypted = $decryptCallback($row['config']);
41-
if ($decrypted !== '') {
42-
$writeConnection->update(
43-
$table,
44-
['config' => $encryptCallback($decrypted)],
45-
['destination_id = ?' => $row['destination_id']],
46-
);
47-
}
48-
}
49-
26+
Mage::helper('core')->recryptTable(
27+
Mage::getSingleton('core/resource')->getTableName('feedmanager/destination'),
28+
'destination_id',
29+
['config'],
30+
$encryptCallback,
31+
$decryptCallback,
32+
);
5033
$output->writeln('OK');
5134
}
5235
}

lib/MahoCLI/Commands/SysEncryptionKeyRegenerate.php

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
123123
$output->writeln('<comment>Skipping re-encryption of existing data (M1 key without mcrypt support).</comment>');
124124
$output->writeln('<comment>Old encrypted data will no longer be decryptable. New data will use the new key.</comment>');
125125
} else {
126-
$this->recryptAdminUserTable($output, $readConnection, $writeConnection);
126+
$this->recryptAdminUserTable($output);
127127
$this->recryptCoreConfigDataTable($output, $readConnection, $writeConnection);
128128
Mage::app()->getCache()->clean('config');
129129

@@ -151,24 +151,16 @@ protected function execute(InputInterface $input, OutputInterface $output): int
151151
return Command::SUCCESS;
152152
}
153153

154-
protected function recryptAdminUserTable(OutputInterface $output, \Maho\Db\Adapter\AdapterInterface $readConnection, \Maho\Db\Adapter\AdapterInterface $writeConnection): void
154+
protected function recryptAdminUserTable(OutputInterface $output): void
155155
{
156156
$output->write('Re-encrypting data on admin_user table... ');
157-
158-
$table = Mage::getSingleton('core/resource')->getTableName('admin_user');
159-
$select = $readConnection->select()
160-
->from($table)
161-
->where('twofa_secret IS NOT NULL');
162-
$encryptedData = $readConnection->fetchAll($select);
163-
164-
foreach ($encryptedData as $encryptedDataRow) {
165-
$writeConnection->update(
166-
$table,
167-
['twofa_secret' => $this->encrypt($this->decrypt($encryptedDataRow['twofa_secret']))],
168-
['user_id = ?' => $encryptedDataRow['user_id']],
169-
);
170-
}
171-
157+
Mage::helper('core')->recryptTable(
158+
Mage::getSingleton('core/resource')->getTableName('admin_user'),
159+
'user_id',
160+
['twofa_secret'],
161+
[$this, 'encrypt'],
162+
[$this, 'decrypt'],
163+
);
172164
$output->writeln('OK');
173165
}
174166

0 commit comments

Comments
 (0)