1 /*
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "accesstokenid_test.h"
17 #include <cstdio>
18 #include <cstdlib>
19 #include <fcntl.h>
20 #include <cerrno>
21 #include <unistd.h>
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <sys/mman.h>
25 #include <sys/wait.h>
26 #include <sys/ioctl.h>
27 #include <ctime>
28 #include <climits>
29 #include <pthread.h>
30 #include <sys/syscall.h>
31 #include <grp.h>
32
33 constexpr unsigned char ACCESS_TOKEN_ID_IOCTL_BASE = 'A';
34 constexpr unsigned int GET_TOKEN_ID = 1;
35 constexpr unsigned int SET_TOKEN_ID = 2;
36 constexpr unsigned int GET_FTOKEN_ID = 3;
37 constexpr unsigned int SET_FTOKEN_ID = 4;
38 constexpr unsigned int ACCESS_TOKENID_MAX_NR = 5;
39 #define ACCESS_TOKENID_GET_TOKENID \
40 _IOR(ACCESS_TOKEN_ID_IOCTL_BASE, GET_TOKEN_ID, unsigned long long)
41 #define ACCESS_TOKENID_SET_TOKENID \
42 _IOW(ACCESS_TOKEN_ID_IOCTL_BASE, SET_TOKEN_ID, unsigned long long)
43 #define ACCESS_TOKENID_GET_FTOKENID \
44 _IOR(ACCESS_TOKEN_ID_IOCTL_BASE, GET_FTOKEN_ID, unsigned long long)
45 #define ACCESS_TOKENID_SET_FTOKENID \
46 _IOW(ACCESS_TOKEN_ID_IOCTL_BASE, SET_FTOKEN_ID, unsigned long long)
47 #define ACCESS_TOKENID_ILLEGAL1 \
48 _IOW(ACCESS_TOKEN_ID_IOCTL_BASE, 0, unsigned long long)
49 #define ACCESS_TOKENID_ILLEGAL2 \
50 _IOW(ACCESS_TOKEN_ID_IOCTL_BASE, ACCESS_TOKENID_MAX_NR, unsigned long long)
51
52 constexpr unsigned long long INVAL_TOKEN = 0xffffffffffffffff;
53
54 #define CHILDREN_NUM 3
55 #define WAIT_FOR_SHELL_OP_TIME 1
56 #define FATHER_WAIT_TIME (WAIT_FOR_SHELL_OP_TIME * (CHILDREN_NUM + 1))
57
58 constexpr unsigned int ACCESS_TOKEN_GRPID = 3020;
59 constexpr unsigned int ACCESS_TOKEN_OTHER_UID = 1234;
60 constexpr unsigned int ACCESS_TOKEN_OTHER_GRPID = 1234;
61
62 const char dev_accesstokenid[] = "/dev/access_token_id";
63
64 struct tokeninfo {
65 pid_t pid;
66 pid_t tid;
67 unsigned long long tokenid;
68 unsigned long long ftokenid;
69 };
70
71 namespace {
GenRand64(void)72 static unsigned long long GenRand64(void)
73 {
74 struct timespec time = {0, 0};
75 unsigned long long randvalue = 0;
76 int fd;
77
78 fd = open("/dev/random", O_RDONLY);
79 if (fd > 0) {
80 read(fd, &randvalue, sizeof(unsigned long long));
81 }
82 close(fd);
83
84 sleep(1);
85 clock_gettime(CLOCK_REALTIME, &time);
86
87 return randvalue + time.tv_nsec;
88 }
89
GetTokenid(unsigned long long * token)90 static int GetTokenid(unsigned long long *token)
91 {
92 int fd = open(dev_accesstokenid, O_RDWR);
93 if (fd < 0) {
94 printf("open %s failed\r\n", dev_accesstokenid);
95 return -1;
96 }
97
98 int ret = ioctl(fd, ACCESS_TOKENID_GET_TOKENID, token);
99 if (ret) {
100 printf("ioctl ACCESS_TOKENID_GET_TOKENID failed\r\n");
101 close(fd);
102 return -1;
103 }
104
105 close(fd);
106 return 0;
107 }
108
SetTokenid(unsigned long long * token)109 static int SetTokenid(unsigned long long *token)
110 {
111 int fd = open(dev_accesstokenid, O_RDWR);
112 if (fd < 0) {
113 printf("open %s failed\r\n", dev_accesstokenid);
114 return -1;
115 }
116
117 int ret = ioctl(fd, ACCESS_TOKENID_SET_TOKENID, token);
118 if (ret) {
119 printf("ioctl ACCESS_TOKENID_SET_TOKENID failed\r\n");
120 close(fd);
121 return -1;
122 }
123
124 close(fd);
125 return 0;
126 }
127
GetfTokenid(unsigned long long * ftoken)128 static int GetfTokenid(unsigned long long *ftoken)
129 {
130 int fd = open(dev_accesstokenid, O_RDWR);
131 if (fd < 0) {
132 printf("open %s failed\r\n", dev_accesstokenid);
133 return -1;
134 }
135
136 int ret = ioctl(fd, ACCESS_TOKENID_GET_FTOKENID, ftoken);
137 if (ret) {
138 printf("ioctl ACCESS_TOKENID_GET_FTOKENID failed\r\n");
139 close(fd);
140 return -1;
141 }
142
143 close(fd);
144 return 0;
145 }
146
SetfTokenid(unsigned long long * ftoken)147 static int SetfTokenid(unsigned long long *ftoken)
148 {
149 int fd = open(dev_accesstokenid, O_RDWR);
150 if (fd < 0) {
151 printf("open %s failed\r\n", dev_accesstokenid);
152 return -1;
153 }
154
155 int ret = ioctl(fd, ACCESS_TOKENID_SET_FTOKENID, ftoken);
156 if (ret) {
157 printf("ioctl ACCESS_TOKENID_SET_FTOKENID failed\r\n");
158 close(fd);
159 return -1;
160 }
161
162 close(fd);
163 return 0;
164 }
165
GetCurToken(unsigned long long * token,unsigned long long * ftoken)166 static void GetCurToken(unsigned long long *token, unsigned long long *ftoken)
167 {
168 GetTokenid(token);
169 GetfTokenid(ftoken);
170
171 return;
172 }
173
CheckChildThreadInheritance(void * args)174 static void *CheckChildThreadInheritance(void *args)
175 {
176 struct tokeninfo *tinfo = (struct tokeninfo *) args;
177
178 tinfo->pid = getpid();
179 tinfo->tid = gettid();
180 GetTokenid(&(tinfo->tokenid));
181 GetfTokenid(&(tinfo->ftokenid));
182
183 pthread_exit(nullptr);
184 return nullptr;
185 }
186
CheckChildThreadSetIndepent(void * args)187 static void *CheckChildThreadSetIndepent(void *args)
188 {
189 struct tokeninfo *tinfo = (struct tokeninfo *) args;
190 unsigned long long tokenSet = GenRand64();
191 unsigned long long ftokenSet = GenRand64();
192 unsigned long long tokenidGet = INVAL_TOKEN;
193 unsigned long long ftokenidGet = INVAL_TOKEN;
194
195 tinfo->pid = getpid();
196 tinfo->tid = gettid();
197
198 GetTokenid(&tokenidGet);
199 GetfTokenid(&ftokenidGet);
200 SetTokenid(&tokenSet);
201 SetfTokenid(&ftokenSet);
202 GetTokenid(&(tinfo->tokenid));
203 GetfTokenid(&(tinfo->ftokenid));
204
205 /* Indicate that the tokenid setting of each child thread does not met requirements. */
206 if (ftokenidGet == 0 && tinfo->tokenid == tokenSet && tinfo->ftokenid == ftokenSet && tinfo->ftokenid != 0) {
207 tinfo->ftokenid = INVAL_TOKEN;
208 }
209
210 pthread_exit(nullptr);
211 return nullptr;
212 }
213 }
214
215 using namespace testing::ext;
216 using namespace std;
217
SetUp()218 void AccesstokenidTest::SetUp() {}
219
TearDown()220 void AccesstokenidTest::TearDown() {}
221
SetUpTestCase()222 void AccesstokenidTest::SetUpTestCase() {}
223
TearDownTestCase()224 void AccesstokenidTest::TearDownTestCase() {}
225
226 /**
227 * @tc.name: CheckInitToken
228 * @tc.desc: Test init value of tokenid and ftokenid
229 * @tc.desc: tokenid equals to the father(hdcd) and ftokenid equals to 0
230 * @tc.type: FUNC
231 */
232 HWTEST_F(AccesstokenidTest, CheckInitToken, Function | MediumTest | Level1)
233 {
234 unsigned long long token = INVAL_TOKEN;
235 unsigned long long ftoken = INVAL_TOKEN;
236
237 GetCurToken(&token, &ftoken);
238
239 /* /data/service/el0/access_token/nativetoken.json
240 {"processName":"hdcd","APL":3,"version":1,"tokenId":680034571,"tokenAttr":0,"dcaps":[]}
241 */
242 ASSERT_NE(0, token);
243 ASSERT_EQ(0, ftoken);
244 }
245
246 /**
247 * @tc.name: CheckSetTokenid
248 * @tc.desc: Test setting of tokenid
249 * @tc.desc: tokenid equals to the setting value
250 * @tc.type: FUNC
251 */
252 HWTEST_F(AccesstokenidTest, CheckSetTokenid, Function | MediumTest | Level1)
253 {
254 unsigned long long token = INVAL_TOKEN;
255 unsigned long long tokenSet = GenRand64();
256
257 SetTokenid(&tokenSet);
258 GetTokenid(&token);
259
260 ASSERT_EQ(tokenSet, token);
261 }
262
263 /**
264 * @tc.name: CheckSetfTokenid
265 * @tc.desc: Test setting of ftokenid
266 * @tc.desc: ftokenid equals to the setting value
267 * @tc.type: FUNC
268 */
269 HWTEST_F(AccesstokenidTest, CheckSetfTokenid, Function | MediumTest | Level1)
270 {
271 unsigned long long ftoken = INVAL_TOKEN;
272 unsigned long long ftokenSet = GenRand64();
273
274 SetfTokenid(&ftokenSet);
275 GetfTokenid(&ftoken);
276
277 ASSERT_EQ(ftokenSet, ftoken);
278 }
279
280 /**
281 * @tc.name: CheckChildThreadInheritance
282 * @tc.desc: Test each child thread tokenid equals to father process while ftokenid not equals
283 * @tc.desc: The ftokenid of each child thread equals to 0
284 * @tc.type: FUNC
285 */
286 HWTEST_F(AccesstokenidTest, CheckChildThreadInheritance, Function | MediumTest | Level1)
287 {
288 pthread_t cid[10];
289
290 unsigned long long token = INVAL_TOKEN;
291 unsigned long long ftoken = INVAL_TOKEN;
292 unsigned long long tokenSet = GenRand64();
293 unsigned long long ftokenSet = GenRand64();
294
295 struct tokeninfo tinfo;
296 tinfo.pid = getpid();
297 tinfo.tid = gettid();
298 tinfo.tokenid = INVAL_TOKEN;
299 tinfo.ftokenid = INVAL_TOKEN;
300
301 GetTokenid(&token);
302 GetfTokenid(&ftoken);
303
304 SetTokenid(&tokenSet);
305 SetfTokenid(&ftokenSet);
306
307 for (int i = 0; i < 10; i++) {
308 if (pthread_create(&cid[i], nullptr, CheckChildThreadInheritance, &tinfo) != 0) {
309 printf("thread %d (ID %ld) pthread_create error\n", i, cid[i]);
310 }
311
312 if (pthread_join(cid[i], nullptr) != 0) {
313 printf("thread %d (ID %ld) pthread_join error\n", i, cid[i]);
314 }
315
316 ASSERT_EQ(tinfo.tokenid, tokenSet);
317 ASSERT_NE(tinfo.ftokenid, ftokenSet);
318 ASSERT_EQ(0, tinfo.ftokenid);
319 }
320 }
321
322 /**
323 * @tc.name: CheckChildThreadSetIndepent
324 * @tc.desc: Test each child thread tokenid and ftokenid is indepent
325 * @tc.desc: The tokenid and ftokenid of each child thread not equal to father process
326 * @tc.type: FUNC
327 */
328 HWTEST_F(AccesstokenidTest, CheckChildThreadSetIndepent, Function | MediumTest | Level1)
329 {
330 pthread_t cid[10];
331
332 unsigned long long token = INVAL_TOKEN;
333 unsigned long long ftoken = INVAL_TOKEN;
334 unsigned long long tokenSet = GenRand64();
335 unsigned long long ftokenSet = GenRand64();
336
337 struct tokeninfo tinfo;
338 tinfo.pid = getpid();
339 tinfo.tid = gettid();
340 tinfo.tokenid = INVAL_TOKEN;
341 tinfo.ftokenid = INVAL_TOKEN;
342
343 GetTokenid(&token);
344 GetfTokenid(&ftoken);
345
346 SetTokenid(&tokenSet);
347 SetfTokenid(&ftokenSet);
348
349 for (int i = 0; i < 10; i++) {
350 if (pthread_create(&cid[i], nullptr, CheckChildThreadSetIndepent, &tinfo) != 0) {
351 printf("thread %d (ID %ld) pthread_create error\n", i, cid[i]);
352 }
353
354 if (pthread_join(cid[i], nullptr) != 0) {
355 printf("thread %d (ID %ld) pthread_join error\n", i, cid[i]);
356 }
357
358 ASSERT_NE(tinfo.tokenid, tokenSet);
359 ASSERT_NE(tinfo.ftokenid, ftokenSet);
360 ASSERT_NE(0, tinfo.ftokenid);
361 }
362 }
363
364 /**
365 * @tc.name: AbnormalGetTokenid
366 * @tc.desc: Test abnormal ioctl cmd of ACCESS_TOKENID_GET_TOKENID
367 * @tc.desc: using nullptr instead of the address of tokenid to ioctl
368 * @tc.type: FUNC
369 */
370 HWTEST_F(AccesstokenidTest, AbnormalGetTokenid, Function | MediumTest | Level1)
371 {
372 int fd = open(dev_accesstokenid, O_RDWR);
373 if (fd < 0) {
374 printf("open %s failed\r\n", dev_accesstokenid);
375 return;
376 }
377
378 int ret = ioctl(fd, ACCESS_TOKENID_GET_TOKENID, nullptr);
379 close(fd);
380
381 ASSERT_NE(0, ret);
382 }
383
384 /**
385 * @tc.name: AbnormalSetTokenid
386 * @tc.desc: Test abnormal ioctl cmd of ACCESS_TOKENID_SET_TOKENID
387 * @tc.desc: using nullptr instead of the address of tokenid to ioctl
388 * @tc.type: FUNC
389 */
390 HWTEST_F(AccesstokenidTest, AbnormalSetTokenid, Function | MediumTest | Level1)
391 {
392 int fd = open(dev_accesstokenid, O_RDWR);
393 if (fd < 0) {
394 printf("open %s failed\r\n", dev_accesstokenid);
395 return;
396 }
397
398 int ret = ioctl(fd, ACCESS_TOKENID_SET_TOKENID, nullptr);
399 close(fd);
400
401 ASSERT_NE(0, ret);
402 }
403
404 /**
405 * @tc.name: AbnormalGetfTokenid
406 * @tc.desc: Test abnormal ioctl cmd of ACCESS_TOKENID_GET_FTOKENID
407 * @tc.desc: using nullptr instead of the address of ftokenid to ioctl
408 * @tc.type: FUNC
409 */
410 HWTEST_F(AccesstokenidTest, AbnormalGetfTokenid, Function | MediumTest | Level1)
411 {
412 int fd = open(dev_accesstokenid, O_RDWR);
413 if (fd < 0) {
414 printf("open %s failed\r\n", dev_accesstokenid);
415 return;
416 }
417
418 int ret = ioctl(fd, ACCESS_TOKENID_GET_FTOKENID, nullptr);
419 close(fd);
420
421 ASSERT_NE(0, ret);
422 }
423
424 /**
425 * @tc.name: AbnormalSetfTokenid
426 * @tc.desc: Test abnormal ioctl cmd of ACCESS_TOKENID_SET_FTOKENID
427 * @tc.desc: using nullptr instead of the address of ftokenid to ioctl
428 * @tc.type: FUNC
429 */
430 HWTEST_F(AccesstokenidTest, AbnormalSetfTokenid, Function | MediumTest | Level1)
431 {
432 int fd = open(dev_accesstokenid, O_RDWR);
433 if (fd < 0) {
434 printf("open %s failed\r\n", dev_accesstokenid);
435 return;
436 }
437
438 int ret = ioctl(fd, ACCESS_TOKENID_SET_FTOKENID, nullptr);
439 close(fd);
440
441 ASSERT_NE(0, ret);
442 }
443
444 /**
445 * @tc.name: AbnormalIoctlCmd
446 * @tc.desc: Test abnormal ioctl cmd of ACCESS_TOKENID_ILLEGAL1 and ACCESS_TOKENID_ILLEGAL1
447 * @tc.desc: using illegal cmd instead of accesstokenid to ioctl
448 * @tc.type: FUNC
449 */
450 HWTEST_F(AccesstokenidTest, AbnormalIoctlCmd, Function | MediumTest | Level1)
451 {
452 unsigned long long token;
453
454 int fd = open(dev_accesstokenid, O_RDWR);
455 if (fd < 0) {
456 printf("open %s failed\r\n", dev_accesstokenid);
457 return;
458 }
459
460 int ret1 = ioctl(fd, ACCESS_TOKENID_ILLEGAL1, &token);
461 int ret2 = ioctl(fd, ACCESS_TOKENID_ILLEGAL2, &token);
462 close(fd);
463
464 ASSERT_NE(0, ret1);
465 ASSERT_NE(0, ret2);
466 }
467
468 /**
469 * @tc.name: OtherUidSetTokenid
470 * @tc.desc: Test ACCESS_TOKEN_OTHER_UID can not set tokenid
471 * @tc.desc: tokenid can be only set by uid 3020
472 * @tc.type: FUNC
473 */
474 HWTEST_F(AccesstokenidTest, OtherUidSetTokenid, Function | MediumTest | Level1)
475 {
476 unsigned long long tokenSet = GenRand64();
477 int ret;
478
479 ret = setuid(ACCESS_TOKEN_OTHER_UID);
480 if (ret != 0) {
481 printf("setuid error %d \r\n", ret);
482 }
483
484 int fd = open(dev_accesstokenid, O_RDWR);
485 if (fd < 0) {
486 printf("open %s failed\r\n", dev_accesstokenid);
487 return;
488 }
489
490 ret = ioctl(fd, ACCESS_TOKENID_SET_TOKENID, &tokenSet);
491 close(fd);
492
493 ASSERT_NE(0, ret);
494 }
495
496 /**
497 * @tc.name: OtherUidGetTokenid
498 * @tc.desc: Test ACCESS_TOKEN_OTHER_UID can get tokenid
499 * @tc.desc: tokenid can get not only by uid 3020
500 * @tc.type: FUNC
501 */
502 HWTEST_F(AccesstokenidTest, OtherUidGetTokenid, Function | MediumTest | Level1)
503 {
504 unsigned long long token = INVAL_TOKEN;
505 int ret;
506
507 ret = setuid(ACCESS_TOKEN_OTHER_UID);
508 if (ret != 0) {
509 printf("setuid error %d \r\n", ret);
510 }
511
512 int fd = open(dev_accesstokenid, O_RDWR);
513 if (fd < 0) {
514 printf("open %s failed\r\n", dev_accesstokenid);
515 return;
516 }
517
518 ret = ioctl(fd, ACCESS_TOKENID_GET_TOKENID, &token);
519 close(fd);
520
521 ASSERT_EQ(0, ret);
522 }
523
524 /**
525 * @tc.name: WithoutGrpSetfTokenid
526 * @tc.desc: Test ACCESS_TOKEN_OTHER_GRPID can not set ftokenid
527 * @tc.desc: ftokenid can not set by groups without grpid 3020
528 * @tc.type: FUNC
529 */
530 HWTEST_F(AccesstokenidTest, WithoutGrpSetfTokenid, Function | MediumTest | Level1)
531 {
532 int ret;
533 size_t size = 1;
534 gid_t list[1] = {ACCESS_TOKEN_OTHER_GRPID};
535 unsigned long long ftokenSet = GenRand64();
536
537 ret = setgroups(size, list);
538 if (ret != 0) {
539 printf("setgroups error %d \r\n", ret);
540 }
541
542 int fd = open(dev_accesstokenid, O_RDWR);
543 if (fd < 0) {
544 printf("open %s failed\r\n", dev_accesstokenid);
545 return;
546 }
547
548 ret = ioctl(fd, ACCESS_TOKENID_SET_FTOKENID, &ftokenSet);
549 close(fd);
550
551 ASSERT_NE(0, ret);
552 }
553
554 /**
555 * @tc.name: WithoutGrpGetfTokenid
556 * @tc.desc: Test ACCESS_TOKEN_OTHER_GRPID can not get ftokenid
557 * @tc.desc: ftokenid can not get by groups without grpid 3020
558 * @tc.type: FUNC
559 */
560 HWTEST_F(AccesstokenidTest, WithoutGrpGetfTokenid, Function | MediumTest | Level1)
561 {
562 int ret;
563 size_t size = 1;
564 gid_t list[1] = {ACCESS_TOKEN_OTHER_GRPID};
565 unsigned long long ftoken = INVAL_TOKEN;
566
567 ret = setgroups(size, list);
568 if (ret != 0) {
569 printf("setgroups error %d \r\n", ret);
570 }
571
572 int fd = open(dev_accesstokenid, O_RDWR);
573 if (fd < 0) {
574 printf("open %s failed\r\n", dev_accesstokenid);
575 return;
576 }
577
578 ret = ioctl(fd, ACCESS_TOKENID_GET_FTOKENID, &ftoken);
579 close(fd);
580
581 ASSERT_NE(0, ret);
582 }
583
584 /**
585 * @tc.name: WithGrpSetfTokenid
586 * @tc.desc: Test ACCESS_TOKEN_OTHER_GRPID and ACCESS_TOKEN_GRPID can set ftokenid
587 * @tc.desc: ftokenid can set by groups with grpid 3020
588 * @tc.type: FUNC
589 */
590 HWTEST_F(AccesstokenidTest, WithGrpSetfTokenid, Function | MediumTest | Level1)
591 {
592 int ret;
593 size_t size = 2;
594 gid_t list[2] = {ACCESS_TOKEN_OTHER_GRPID, ACCESS_TOKEN_GRPID};
595 unsigned long long ftokenSet = GenRand64();
596
597 ret = setgroups(size, list);
598 if (ret != 0) {
599 printf("setgroups error %d \r\n", ret);
600 }
601
602 int fd = open(dev_accesstokenid, O_RDWR);
603 if (fd < 0) {
604 printf("open %s failed\r\n", dev_accesstokenid);
605 return;
606 }
607
608 ret = ioctl(fd, ACCESS_TOKENID_SET_FTOKENID, &ftokenSet);
609 close(fd);
610
611 ASSERT_EQ(0, ret);
612 }
613
614 /**
615 * @tc.name: WithGrpGetfTokenid
616 * @tc.desc: Test ACCESS_TOKEN_OTHER_GRPID and ACCESS_TOKEN_GRPID can set ftokenid
617 * @tc.desc: ftokenid can set by groups with grpid 3020
618 * @tc.type: FUNC
619 */
620 HWTEST_F(AccesstokenidTest, WithGrpGetfTokenid, Function | MediumTest | Level1)
621 {
622 int ret;
623 size_t size = 2;
624 gid_t list[2] = {ACCESS_TOKEN_OTHER_GRPID, ACCESS_TOKEN_GRPID};
625 unsigned long long ftoken = INVAL_TOKEN;
626
627 ret = setgroups(size, list);
628 if (ret != 0) {
629 printf("setgroups error %d \r\n", ret);
630 }
631
632 int fd = open(dev_accesstokenid, O_RDWR);
633 if (fd < 0) {
634 printf("open %s failed\r\n", dev_accesstokenid);
635 return;
636 }
637
638 ret = ioctl(fd, ACCESS_TOKENID_GET_FTOKENID, &ftoken);
639 close(fd);
640
641 ASSERT_EQ(0, ret);
642 }
643
644