Skip to content

Commit 43b95cb

Browse files
committed
external: normalize unambiguous hex file id lookup
external_lookup_id() uses strict string comparison, so file[0xa:0xa] and file[10:10] (the same file) wont match when one side emits hex and the other decimal. Add a numeric comparison path, but only for unambiguous forms to preserve backward compatibility. The key insight from avagins review: a component like 11 is ambiguous it could mean decimal 11 or hex (0x11 = 17 decimal). Without the explicit 0x prefix, there is no way to know the users intent. file[11:17] and file[17:23] could silently match incorrectly. Parse rules: - 0x... => unambiguous hex (explicit prefix) - contains hex digits (a-f/A-F) => unambiguous hex - digits only => ambiguous, do NOT normalize-match Only normalize when BOTH the mnt_id AND inode components are unambiguous. Ambiguous IDs retain their literal/exact semantics. This narrow fix addresses backward compatibility concerns: - CRIU emits bare hex from %x (e.g., file[a:a]): unambiguous, normalizes - CRIU emits bare digits from %x (e.g., file[10:10]): ambiguous, doesnt normalize - User provides file[0xa:0xa]: unambiguous, matches to stored file[0xa:0xa] - User provides file[10:10]: ambiguous, matches to stored file[10:10] - file[10:10] will NOT normalize-match file[0xa:0xa] because the latter is unambiguous The previous approach was too broad. This explicitly documents where compatibility is preserved and where it is not. Fixes: #2951 Signed-off-by: Farzan Aman Khan <farzanaman99@gmail.com>
1 parent c180188 commit 43b95cb

1 file changed

Lines changed: 110 additions & 1 deletion

File tree

criu/external.c

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
#include <errno.h>
2+
#include <limits.h>
3+
#include <stdint.h>
4+
#include <stdlib.h>
5+
16
#include "common/err.h"
27
#include "common/list.h"
38
#include "cr_options.h"
@@ -8,6 +13,89 @@
813

914
#include "net.h"
1015

16+
static bool parse_external_id_component(const char *id, char delim, char **end, unsigned long long *val, bool * unambiguous)
17+
{
18+
char *tmp;
19+
bool has_hex_digits = false;
20+
21+
/*
22+
* Parse with explicit hex prefix (0x...) as hex.
23+
* If no 0x prefix, check whether the component contains
24+
* hex digits (a-f/A-F). If it does, it's unambiguously hex.
25+
* If it's only decimal digits, mark as ambiguous -- we cannot
26+
* infer whether the user meant decimal or hex without the
27+
* explicit prefix.
28+
*
29+
* This avoids the ambiguity where file[11:17] could mean
30+
* decimal (11, 17) or hex (17=23 decimal). Only normalize
31+
* when both IDs are unambiguous to prevent silent miscorrection.
32+
*/
33+
if (id[0] == '0' && (id[1] == 'x' || id[1] == 'X')) {
34+
errno = 0;
35+
*val = strtoull(id, &tmp, 16);
36+
if (errno || tmp == id || *tmp != delim)
37+
return false;
38+
*end = tmp;
39+
*unambiguous = true;
40+
return true;
41+
}
42+
43+
/* Check for hex digits in the component (a-f/A-F) */
44+
for (tmp = (char *)id; tmp < (char *)(id + 20) && *tmp != delim && *tmp != '\0'; tmp++) {
45+
if ((*tmp >= 'a' && *tmp <= 'f') || (*tmp >= 'A' && *tmp <= 'F')) {
46+
has_hex_digits = true;
47+
break;
48+
}
49+
}
50+
51+
if (has_hex_digits) {
52+
errno = 0;
53+
*val = strtoull(id, &tmp, 16);
54+
if (errno || tmp == id || *tmp != delim)
55+
return false;
56+
*end = tmp;
57+
*unambiguous = true;
58+
return true;
59+
}
60+
61+
/* Digits only -- ambiguous, could be decimal or hex */
62+
errno = 0;
63+
*val = strtoull(id, &tmp, 10);
64+
if (!errno && tmp != id && *tmp == delim) {
65+
*end = tmp;
66+
*unambiguous = false;
67+
return true;
68+
}
69+
70+
return false;
71+
}
72+
static bool parse_external_file_id(const char *id, unsigned int *mnt_id, uint64_t *inode, bool *unambiguous)
73+
{
74+
char *end = NULL;
75+
unsigned long long val;
76+
bool mnt_unambiguous, inode_unambiguous;
77+
78+
if (!strstartswith(id, "file["))
79+
return false;
80+
id += strlen("file[");
81+
82+
if (!parse_external_id_component(id, ':', &end, &val, &mnt_unambiguous))
83+
return false;
84+
if (val > UINT_MAX)
85+
return false;
86+
*mnt_id = (unsigned int)val;
87+
88+
id = end + 1;
89+
if (!parse_external_id_component(id, ']', &end, &val, &inode_unambiguous))
90+
return false;
91+
end++;
92+
if (*end != '\0')
93+
return false;
94+
95+
*inode = val;
96+
*unambiguous = mnt_unambiguous && inode_unambiguous;
97+
return true;
98+
}
1199
int add_external(char *key)
12100
{
13101
struct external *ext;
@@ -39,10 +127,31 @@ int add_external(char *key)
39127
bool external_lookup_id(char *id)
40128
{
41129
struct external *ext;
130+
unsigned int id_mnt_id, ext_mnt_id;
131+
uint64_t id_inode, ext_inode;
132+
bool id_unambiguous, ext_unambiguous;
42133

43-
list_for_each_entry(ext, &opts.external, node)
134+
if (!parse_external_file_id(id, &id_mnt_id, &id_inode, &id_unambiguous))
135+
id_unambiguous = false;
136+
137+
list_for_each_entry(ext, &opts.external, node) {
44138
if (!strcmp(ext->id, id))
45139
return true;
140+
141+
/*
142+
* Only normalize when BOTH the queried ID and the stored ID
143+
* are unambiguous (explicit 0x prefix or explicit hex digits).
144+
* Ambiguous IDs (digit-only like 11) retain their literal semantics
145+
* and are not matched by normalization to avoid silent miscorrection.
146+
* This preserves backward compatibility with existing checkpoint
147+
* images while enabling proper matching for unambiguous forms.
148+
*/
149+
if (id_unambiguous &&
150+
parse_external_file_id(ext->id, &ext_mnt_id, &ext_inode, &ext_unambiguous) &&
151+
ext_unambiguous &&
152+
ext_mnt_id == id_mnt_id && ext_inode == id_inode)
153+
return true;
154+
}
46155
return false;
47156
}
48157

0 commit comments

Comments
 (0)